Bug 771617 - Refactoring to better share code between implementations of OS.File. r=froydnj

This commit is contained in:
David Rajchenbach-Teller 2012-08-25 17:18:43 -04:00
parent c8d9e67334
commit e7229b5ea3
9 changed files with 368 additions and 324 deletions

View File

@ -39,3 +39,4 @@ libs::
$(NSINSTALL) $(srcdir)/ospath_win_back.jsm $(FINAL_TARGET)/modules/osfile
$(NSINSTALL) $(srcdir)/osfile_win_back.jsm $(FINAL_TARGET)/modules/osfile
$(NSINSTALL) $(srcdir)/osfile_win_front.jsm $(FINAL_TARGET)/modules/osfile
$(NSINSTALL) $(srcdir)/osfile_shared_front.jsm $(FINAL_TARGET)/modules/osfile

View File

@ -905,74 +905,5 @@
}
return Strings.importWString(decoded);
};
/**
* Specific tools that don't really fit anywhere.
*/
let _aux = {};
exports.OS.Shared._aux = _aux;
/**
* Utility function shared by implementations of |OS.File.open|:
* extract read/write/trunc/create/existing flags from a |mode|
* object.
*
* @param {*=} mode An object that may contain fields |read|,
* |write|, |truncate|, |create|, |existing|. These fields
* are interpreted only if true-ish.
* @return {{read:bool, write:bool, trunc:bool, create:bool,
* existing:bool}} an object recapitulating the options set
* by |mode|.
* @throws {TypeError} If |mode| contains other fields, or
* if it contains both |create| and |truncate|, or |create|
* and |existing|.
*/
_aux.normalizeOpenMode = function normalizeOpenMode(mode) {
let result = {
read: false,
write: false,
trunc: false,
create: false,
existing: false
};
for (let key in mode) {
if (!mode[key]) continue; // Only interpret true-ish keys
switch (key) {
case "read":
result.read = true;
break;
case "write":
result.write = true;
break;
case "truncate": // fallthrough
case "trunc":
result.trunc = true;
result.write = true;
break;
case "create":
result.create = true;
result.write = true;
break;
case "existing": // fallthrough
case "exist":
result.existing = true;
break;
default:
throw new TypeError("Mode " + key + " not understood");
}
}
// Reject opposite modes
if (result.existing && result.create) {
throw new TypeError("Cannot specify both existing:true and create:true");
}
if (result.trunc && result.create) {
throw new TypeError("Cannot specify both trunc:true and create:true");
}
// Handle read/write
if (!result.write) {
result.read = true;
}
return result;
};
})(this);
}

View File

@ -0,0 +1,106 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Code shared by OS.File front-ends.
*
* This code is meant to be included by another library. It is also meant to
* be executed only on a worker thread.
*/
if (typeof Components != "undefined") {
throw new Error("osfile_shared_front.jsm cannot be used from the main thread");
}
(function(exports) {
/**
* Code shared by implementations of File.
*
* @param {*} fd An OS-specific file handle.
* @constructor
*/
let AbstractFile = function AbstractFile(fd) {
this._fd = fd;
};
AbstractFile.prototype = {
/**
* Return the file handle.
*
* @throw OS.File.Error if the file has been closed.
*/
get fd() {
if (this._fd) {
return this._fd;
}
throw OS.File.Error.closed();
}
};
/**
* Utility function shared by implementations of |OS.File.open|:
* extract read/write/trunc/create/existing flags from a |mode|
* object.
*
* @param {*=} mode An object that may contain fields |read|,
* |write|, |truncate|, |create|, |existing|. These fields
* are interpreted only if true-ish.
* @return {{read:bool, write:bool, trunc:bool, create:bool,
* existing:bool}} an object recapitulating the options set
* by |mode|.
* @throws {TypeError} If |mode| contains other fields, or
* if it contains both |create| and |truncate|, or |create|
* and |existing|.
*/
AbstractFile.normalizeOpenMode = function normalizeOpenMode(mode) {
let result = {
read: false,
write: false,
trunc: false,
create: false,
existing: false
};
for (let key in mode) {
if (!mode[key]) continue; // Only interpret true-ish keys
switch (key) {
case "read":
result.read = true;
break;
case "write":
result.write = true;
break;
case "truncate": // fallthrough
case "trunc":
result.trunc = true;
result.write = true;
break;
case "create":
result.create = true;
result.write = true;
break;
case "existing": // fallthrough
case "exist":
result.existing = true;
break;
default:
throw new TypeError("Mode " + key + " not understood");
}
}
// Reject opposite modes
if (result.existing && result.create) {
throw new TypeError("Cannot specify both existing:true and create:true");
}
if (result.trunc && result.create) {
throw new TypeError("Cannot specify both trunc:true and create:true");
}
// Handle read/write
if (!result.write) {
result.read = true;
}
return result;
};
exports.OS.Shared.AbstractFile = AbstractFile;
})(this);

View File

@ -131,6 +131,15 @@ if (typeof Components != "undefined") {
return this.unixErrno == OS.Constants.libc.ENOTEMPTY;
}
});
/**
* |true| if the error was raised because a file or directory
* is closed, |false| otherwise.
*/
Object.defineProperty(OSError.prototype, "becauseClosed", {
get: function becauseClosed() {
return this.unixErrno == OS.Constants.libc.EBADF;
}
});
/**
* Serialize an instance of OSError to something that can be
@ -169,4 +178,9 @@ if (typeof Components != "undefined") {
*/
Types.path = Types.cstring.withName("[in] path");
Types.out_path = Types.out_cstring.withName("[out] path");
// Special constructors that need to be defined on all threads
OSError.closed = function closed(operation) {
return new OSError(operation, OS.Constants.libc.EBADF);
};
})(this);

View File

@ -178,7 +178,7 @@
// Declare libc functions as functions of |OS.Unix.File|
// Finalizer-related functions
let _close =
let _close = UnixFile._close =
libc.declare("close", ctypes.default_abi,
/*return */ctypes.int,
/*fd*/ ctypes.int);

View File

@ -18,6 +18,7 @@
}
importScripts("resource://gre/modules/osfile/osfile_unix_back.jsm");
importScripts("resource://gre/modules/osfile/ospath_unix_back.jsm");
importScripts("resource://gre/modules/osfile/osfile_shared_front.jsm");
(function(exports) {
"use strict";
@ -40,133 +41,122 @@
* @constructor
*/
let File = function File(fd) {
this._fd = fd;
exports.OS.Shared.AbstractFile.call(this, fd);
this._closeResult = null;
};
File.prototype = {
/**
* If the file is open, this returns the file descriptor.
* Otherwise, throw a |File.Error|.
*/
get fd() {
return this._fd;
},
File.prototype = Object.create(exports.OS.Shared.AbstractFile.prototype);
// Placeholder getter, used to replace |get fd| once
// the file is closed.
_nofd: function nofd(operation) {
operation = operation || "unknown operation";
throw new File.Error(operation, Const.EBADF);
},
/**
* Close the file.
*
* This method has no effect if the file is already closed. However,
* if the first call to |close| has thrown an error, further calls
* will throw the same error.
*
* @throws File.Error If closing the file revealed an error that could
* not be reported earlier.
*/
close: function close() {
if (this._fd) {
let fd = this._fd;
this._fd = null;
delete this.fd;
Object.defineProperty(this, "fd", {get: File.prototype._nofd});
// Call |close(fd)|, detach finalizer
if (UnixFile.close(fd) == -1) {
this._closeResult = new File.Error("close", ctypes.errno);
}
/**
* Close the file.
*
* This method has no effect if the file is already closed. However,
* if the first call to |close| has thrown an error, further calls
* will throw the same error.
*
* @throws File.Error If closing the file revealed an error that could
* not be reported earlier.
*/
File.prototype.close = function close() {
if (this._fd) {
let fd = this._fd;
this._fd = null;
// Call |close(fd)|, detach finalizer if any
// (|fd| may not be a CDataFinalizer if it has been
// instantiated from a controller thread).
let result = UnixFile._close(fd);
if (typeof fd == "object" && "forget" in fd) {
fd.forget();
}
if (this._closeResult) {
throw this._closeResult;
if (result == -1) {
this._closeResult = new File.Error("close");
}
return;
},
_closeResult: null,
/**
* Read some bytes from a file.
*
* @param {ArrayBuffer} buffer A buffer for holding the data
* once it is read.
* @param {number} nbytes The number of bytes to read. It must not
* exceed the size of |buffer| in bytes but it may exceed the number
* of bytes unread in the file.
* @param {*=} options Additional options for reading. Ignored in
* this implementation.
*
* @return {number} The number of bytes effectively read. If zero,
* the end of the file has been reached.
* @throws {OS.File.Error} In case of I/O error.
*/
read: function read(buffer, nbytes, options) {
return throw_on_negative("read",
UnixFile.read(this.fd, buffer, nbytes)
);
},
/**
* Write some bytes to a file.
*
* @param {ArrayBuffer} buffer A buffer holding the data that must be
* written.
* @param {number} nbytes The number of bytes to write. It must not
* exceed the size of |buffer| in bytes.
* @param {*=} options Additional options for writing. Ignored in
* this implementation.
*
* @return {number} The number of bytes effectively written.
* @throws {OS.File.Error} In case of I/O error.
*/
write: function write(buffer, nbytes, options) {
return throw_on_negative("write",
UnixFile.write(this.fd, buffer, nbytes)
);
},
/**
* Return the current position in the file.
*/
getPosition: function getPosition(pos) {
return this.setPosition(0, File.POS_CURRENT);
},
/**
* Change the current position in the file.
*
* @param {number} pos The new position. Whether this position
* is considered from the current position, from the start of
* the file or from the end of the file is determined by
* argument |whence|. Note that |pos| may exceed the length of
* the file.
* @param {number=} whence The reference position. If omitted
* or |OS.File.POS_START|, |pos| is relative to the start of the
* file. If |OS.File.POS_CURRENT|, |pos| is relative to the
* current position in the file. If |OS.File.POS_END|, |pos| is
* relative to the end of the file.
*
* @return The new position in the file.
*/
setPosition: function setPosition(pos, whence) {
if (whence === undefined) {
whence = Const.SEEK_START;
}
return throw_on_negative("setPosition",
UnixFile.lseek(this.fd, pos, whence)
);
},
/**
* Fetch the information on the file.
*
* @return File.Info The information on |this| file.
*/
stat: function stat() {
throw_on_negative("stat", UnixFile.fstat(this.fd, gStatDataPtr));
return new File.Info(gStatData);
}
if (this._closeResult) {
throw this._closeResult;
}
return;
};
/**
* Read some bytes from a file.
*
* @param {ArrayBuffer} buffer A buffer for holding the data
* once it is read.
* @param {number} nbytes The number of bytes to read. It must not
* exceed the size of |buffer| in bytes but it may exceed the number
* of bytes unread in the file.
* @param {*=} options Additional options for reading. Ignored in
* this implementation.
*
* @return {number} The number of bytes effectively read. If zero,
* the end of the file has been reached.
* @throws {OS.File.Error} In case of I/O error.
*/
File.prototype.read = function read(buffer, nbytes, options) {
return throw_on_negative("read",
UnixFile.read(this.fd, buffer, nbytes)
);
};
/**
* Write some bytes to a file.
*
* @param {ArrayBuffer} buffer A buffer holding the data that must be
* written.
* @param {number} nbytes The number of bytes to write. It must not
* exceed the size of |buffer| in bytes.
* @param {*=} options Additional options for writing. Ignored in
* this implementation.
*
* @return {number} The number of bytes effectively written.
* @throws {OS.File.Error} In case of I/O error.
*/
File.prototype.write = function write(buffer, nbytes, options) {
return throw_on_negative("write",
UnixFile.write(this.fd, buffer, nbytes)
);
};
/**
* Return the current position in the file.
*/
File.prototype.getPosition = function getPosition(pos) {
return this.setPosition(0, File.POS_CURRENT);
};
/**
* Change the current position in the file.
*
* @param {number} pos The new position. Whether this position
* is considered from the current position, from the start of
* the file or from the end of the file is determined by
* argument |whence|. Note that |pos| may exceed the length of
* the file.
* @param {number=} whence The reference position. If omitted
* or |OS.File.POS_START|, |pos| is relative to the start of the
* file. If |OS.File.POS_CURRENT|, |pos| is relative to the
* current position in the file. If |OS.File.POS_END|, |pos| is
* relative to the end of the file.
*
* @return The new position in the file.
*/
File.prototype.setPosition = function setPosition(pos, whence) {
if (whence === undefined) {
whence = Const.SEEK_START;
}
return throw_on_negative("setPosition",
UnixFile.lseek(this.fd, pos, whence)
);
};
/**
* Fetch the information on the file.
*
* @return File.Info The information on |this| file.
*/
File.prototype.stat = function stat() {
throw_on_negative("stat", UnixFile.fstat(this.fd, gStatDataPtr));
return new File.Info(gStatData);
};
@ -226,7 +216,7 @@
if (options.unixFlags) {
flags = options.unixFlags;
} else {
mode = OS.Shared._aux.normalizeOpenMode(mode);
mode = OS.Shared.AbstractFile.normalizeOpenMode(mode);
// Handle read/write
if (!mode.write) {
flags = Const.O_RDONLY;

View File

@ -141,6 +141,15 @@ if (typeof Components != "undefined") {
return this.winLastError == OS.Constants.Win.ERROR_DIR_NOT_EMPTY;
}
});
/**
* |true| if the error was raised because a file or directory
* is closed, |false| otherwise.
*/
Object.defineProperty(OSError.prototype, "becauseClosed", {
get: function becauseClosed() {
return this.winLastError == exports.OS.Constants.Win.INVALID_HANDLE_VALUE;
}
});
/**
* Serialize an instance of OSError to something that can be
@ -179,4 +188,10 @@ if (typeof Components != "undefined") {
*/
Types.path = Types.wstring.withName("[in] path");
Types.out_path = Types.out_wstring.withName("[out] path");
// Special constructors that need to be defined on all threads
OSError.closed = function closed(operation) {
return new OSError(operation, exports.OS.Constants.Win.INVALID_HANDLE_VALUE);
};
})(this);

View File

@ -177,7 +177,7 @@
// Special case: these functions are used by the
// finalizer
let _CloseHandle =
let _CloseHandle = WinFile._CloseHandle =
libc.declare("CloseHandle", ctypes.winapi_abi,
/*return */ctypes.bool,
/*handle*/ ctypes.voidptr_t);

View File

@ -16,9 +16,9 @@
throw new Error("osfile_win_front.jsm cannot be used from the main thread yet");
}
importScripts("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
importScripts("resource://gre/modules/osfile/osfile_win_back.jsm");
importScripts("resource://gre/modules/osfile/ospath_win_back.jsm");
importScripts("resource://gre/modules/osfile/osfile_shared_front.jsm");
(function(exports) {
"use strict";
@ -60,139 +60,126 @@
* @constructor
*/
let File = function File(fd) {
this._fd = fd;
exports.OS.Shared.AbstractFile.call(this, fd);
this._closeResult = null;
};
File.prototype = {
/**
* If the file is open, this returns the file descriptor.
* Otherwise, throw a |File.Error|.
*/
get fd() {
return this._fd;
},
File.prototype = Object.create(exports.OS.Shared.AbstractFile.prototype);
// Placeholder getter, used to replace |get fd| once
// the file is closed.
_nofd: function nofd(operation) {
operation = operation ||
this._nofd.caller.name ||
"unknown operation";
throw new File.Error(operation, Const.INVALID_HANDLE_VALUE);
},
/**
* Close the file.
*
* This method has no effect if the file is already closed. However,
* if the first call to |close| has thrown an error, further calls
* will throw the same error.
*
* @throws File.Error If closing the file revealed an error that could
* not be reported earlier.
*/
close: function close() {
if (this._fd) {
let fd = this._fd;
this._fd = null;
delete this.fd;
Object.defineProperty(this, "fd", {get: File.prototype._nofd});
// Call CloseHandle(fd), detach finalizer
if (fd.dispose() == 0) {
this._closeResult = new File.Error("close", ctypes.errno);
}
/**
* Close the file.
*
* This method has no effect if the file is already closed. However,
* if the first call to |close| has thrown an error, further calls
* will throw the same error.
*
* @throws File.Error If closing the file revealed an error that could
* not be reported earlier.
*/
File.prototype.close = function close() {
if (this._fd) {
let fd = this._fd;
this._fd = null;
// Call |close(fd)|, detach finalizer if any
// (|fd| may not be a CDataFinalizer if it has been
// instantiated from a controller thread).
let result = WinFile._CloseHandle(fd);
if (typeof fd == "object" && "forget" in fd) {
fd.forget();
}
if (this._closeResult) {
throw this._closeResult;
if (result == -1) {
this._closeResult = new File.Error("close");
}
return;
},
_closeResult: null,
/**
* Read some bytes from a file.
*
* @param {ArrayBuffer} buffer A buffer for holding the data
* once it is read.
* @param {number} nbytes The number of bytes to read. It must not
* exceed the size of |buffer| in bytes but it may exceed the number
* of bytes unread in the file.
* @param {*=} options Additional options for reading. Ignored in
* this implementation.
*
* @return {number} The number of bytes effectively read. If zero,
* the end of the file has been reached.
* @throws {OS.File.Error} In case of I/O error.
*/
read: function read(buffer, nbytes, options) {
// |gBytesReadPtr| is a pointer to |gBytesRead|.
throw_on_zero("read",
WinFile.ReadFile(this.fd, buffer, nbytes, gBytesReadPtr, null)
);
return gBytesRead.value;
},
/**
* Write some bytes to a file.
*
* @param {ArrayBuffer} buffer A buffer holding the data that must be
* written.
* @param {number} nbytes The number of bytes to write. It must not
* exceed the size of |buffer| in bytes.
* @param {*=} options Additional options for writing. Ignored in
* this implementation.
*
* @return {number} The number of bytes effectively written.
* @throws {OS.File.Error} In case of I/O error.
*/
write: function write(buffer, nbytes, options) {
// |gBytesWrittenPtr| is a pointer to |gBytesWritten|.
throw_on_zero("write",
WinFile.WriteFile(this.fd, buffer, nbytes, gBytesWrittenPtr, null)
);
return gBytesWritten.value;
},
/**
* Return the current position in the file.
*/
getPosition: function getPosition(pos) {
return this.setPosition(0, File.POS_CURRENT);
},
/**
* Change the current position in the file.
*
* @param {number} pos The new position. Whether this position
* is considered from the current position, from the start of
* the file or from the end of the file is determined by
* argument |whence|. Note that |pos| may exceed the length of
* the file.
* @param {number=} whence The reference position. If omitted
* or |OS.File.POS_START|, |pos| is relative to the start of the
* file. If |OS.File.POS_CURRENT|, |pos| is relative to the
* current position in the file. If |OS.File.POS_END|, |pos| is
* relative to the end of the file.
*
* @return The new position in the file.
*/
setPosition: function setPosition(pos, whence) {
if (whence === undefined) {
whence = Const.FILE_BEGIN;
}
return throw_on_negative("setPosition",
WinFile.SetFilePointer(this.fd, pos, null, whence));
},
/**
* Fetch the information on the file.
*
* @return File.Info The information on |this| file.
*/
stat: function stat() {
throw_on_zero("stat",
WinFile.GetFileInformationByHandle(this.fd, gFileInfoPtr));
return new File.Info(gFileInfo);
}
if (this._closeResult) {
throw this._closeResult;
}
return;
};
/**
* Read some bytes from a file.
*
* @param {ArrayBuffer} buffer A buffer for holding the data
* once it is read.
* @param {number} nbytes The number of bytes to read. It must not
* exceed the size of |buffer| in bytes but it may exceed the number
* of bytes unread in the file.
* @param {*=} options Additional options for reading. Ignored in
* this implementation.
*
* @return {number} The number of bytes effectively read. If zero,
* the end of the file has been reached.
* @throws {OS.File.Error} In case of I/O error.
*/
File.prototype.read = function read(buffer, nbytes, options) {
// |gBytesReadPtr| is a pointer to |gBytesRead|.
throw_on_zero("read",
WinFile.ReadFile(this.fd, buffer, nbytes, gBytesReadPtr, null)
);
return gBytesRead.value;
};
/**
* Write some bytes to a file.
*
* @param {ArrayBuffer} buffer A buffer holding the data that must be
* written.
* @param {number} nbytes The number of bytes to write. It must not
* exceed the size of |buffer| in bytes.
* @param {*=} options Additional options for writing. Ignored in
* this implementation.
*
* @return {number} The number of bytes effectively written.
* @throws {OS.File.Error} In case of I/O error.
*/
File.prototype.write = function write(buffer, nbytes, options) {
// |gBytesWrittenPtr| is a pointer to |gBytesWritten|.
throw_on_zero("write",
WinFile.WriteFile(this.fd, buffer, nbytes, gBytesWrittenPtr, null)
);
return gBytesWritten.value;
};
/**
* Return the current position in the file.
*/
File.prototype.getPosition = function getPosition(pos) {
return this.setPosition(0, File.POS_CURRENT);
};
/**
* Change the current position in the file.
*
* @param {number} pos The new position. Whether this position
* is considered from the current position, from the start of
* the file or from the end of the file is determined by
* argument |whence|. Note that |pos| may exceed the length of
* the file.
* @param {number=} whence The reference position. If omitted
* or |OS.File.POS_START|, |pos| is relative to the start of the
* file. If |OS.File.POS_CURRENT|, |pos| is relative to the
* current position in the file. If |OS.File.POS_END|, |pos| is
* relative to the end of the file.
*
* @return The new position in the file.
*/
File.prototype.setPosition = function setPosition(pos, whence) {
if (whence === undefined) {
whence = Const.FILE_BEGIN;
}
return throw_on_negative("setPosition",
WinFile.SetFilePointer(this.fd, pos, null, whence));
};
/**
* Fetch the information on the file.
*
* @return File.Info The information on |this| file.
*/
File.prototype.stat = function stat() {
throw_on_zero("stat",
WinFile.GetFileInformationByHandle(this.fd, gFileInfoPtr));
return new File.Info(gFileInfo);
};
// Constant used to normalize options.
@ -279,7 +266,7 @@
throw new TypeError("OS.File.open requires either both options " +
"winAccess and winDisposition or neither");
} else {
mode = OS.Shared._aux.normalizeOpenMode(mode);
mode = OS.Shared.AbstractFile.normalizeOpenMode(mode);
if (mode.read) {
access |= Const.GENERIC_READ;
}