mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
838 lines
31 KiB
JavaScript
838 lines
31 KiB
JavaScript
/* 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/. */
|
|
|
|
/**
|
|
* Synchronous front-end for the JavaScript OS.File library.
|
|
* Windows implementation.
|
|
*
|
|
* This front-end is meant to be imported by a worker thread.
|
|
*/
|
|
|
|
{
|
|
if (typeof Components != "undefined") {
|
|
// We do not wish osfile_win_front.jsm to be used directly as a main thread
|
|
// module yet.
|
|
throw new Error("osfile_win_front.jsm cannot be used from the main thread yet");
|
|
}
|
|
|
|
(function(exports) {
|
|
"use strict";
|
|
|
|
// exports.OS.Win is created by osfile_win_back.jsm
|
|
if (exports.OS.File) {
|
|
return; // Avoid double-initialization
|
|
}
|
|
exports.OS.Win.File._init();
|
|
let Const = exports.OS.Constants.Win;
|
|
let WinFile = exports.OS.Win.File;
|
|
let LOG = OS.Shared.LOG.bind(OS.Shared, "Win front-end");
|
|
|
|
// Mutable thread-global data
|
|
// In the Windows implementation, methods |read| and |write|
|
|
// require passing a pointer to an int32 to determine how many
|
|
// bytes have been read/written. In C, this is a benigne operation,
|
|
// but in js-ctypes, this has a cost. Rather than re-allocating a
|
|
// C int32 and a C int32* for each |read|/|write|, we take advantage
|
|
// of the fact that the state is thread-private -- hence that two
|
|
// |read|/|write| operations cannot take place at the same time --
|
|
// and we use the following global mutable values:
|
|
let gBytesRead = new ctypes.int32_t(-1);
|
|
let gBytesReadPtr = gBytesRead.address();
|
|
let gBytesWritten = new ctypes.int32_t(-1);
|
|
let gBytesWrittenPtr = gBytesWritten.address();
|
|
|
|
// Same story for GetFileInformationByHandle
|
|
let gFileInfo = new OS.Shared.Type.FILE_INFORMATION.implementation();
|
|
let gFileInfoPtr = gFileInfo.address();
|
|
|
|
/**
|
|
* Representation of a file.
|
|
*
|
|
* You generally do not need to call this constructor yourself. Rather,
|
|
* to open a file, use function |OS.File.open|.
|
|
*
|
|
* @param fd A OS-specific file descriptor.
|
|
* @constructor
|
|
*/
|
|
let File = function File(fd) {
|
|
exports.OS.Shared.AbstractFile.call(this, fd);
|
|
this._closeResult = null;
|
|
};
|
|
File.prototype = Object.create(exports.OS.Shared.AbstractFile.prototype);
|
|
|
|
/**
|
|
* 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 (result == -1) {
|
|
this._closeResult = new File.Error("close");
|
|
}
|
|
}
|
|
if (this._closeResult) {
|
|
throw this._closeResult;
|
|
}
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* Read some bytes from a file.
|
|
*
|
|
* @param {C pointer} 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 {C pointer} 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);
|
|
};
|
|
|
|
/**
|
|
* Flushes the file's buffers and causes all buffered data
|
|
* to be written.
|
|
*
|
|
* @throws {OS.File.Error} In case of I/O error.
|
|
*/
|
|
File.prototype.flush = function flush() {
|
|
throw_on_zero("flush", WinFile.FlushFileBuffers(this.fd));
|
|
};
|
|
|
|
// Constant used to normalize options.
|
|
const noOptions = {};
|
|
|
|
// The default sharing mode for opening files: files are not
|
|
// locked against being reopened for reading/writing or against
|
|
// being deleted by the same process or another process.
|
|
// This is consistent with the default Unix policy.
|
|
const DEFAULT_SHARE = Const.FILE_SHARE_READ |
|
|
Const.FILE_SHARE_WRITE | Const.FILE_SHARE_DELETE;
|
|
|
|
// The default flags for opening files.
|
|
const DEFAULT_FLAGS = Const.FILE_ATTRIBUTE_NORMAL;
|
|
|
|
/**
|
|
* Open a file
|
|
*
|
|
* @param {string} path The path to the file.
|
|
* @param {*=} mode The opening mode for the file, as
|
|
* an object that may contain the following fields:
|
|
*
|
|
* - {bool} truncate If |true|, the file will be opened
|
|
* for writing. If the file does not exist, it will be
|
|
* created. If the file exists, its contents will be
|
|
* erased. Cannot be specified with |create|.
|
|
* - {bool} create If |true|, the file will be opened
|
|
* for writing. If the file exists, this function fails.
|
|
* If the file does not exist, it will be created. Cannot
|
|
* be specified with |truncate| or |existing|.
|
|
* - {bool} existing. If the file does not exist, this function
|
|
* fails. Cannot be specified with |create|.
|
|
* - {bool} read If |true|, the file will be opened for
|
|
* reading. The file may also be opened for writing, depending
|
|
* on the other fields of |mode|.
|
|
* - {bool} write If |true|, the file will be opened for
|
|
* writing. The file may also be opened for reading, depending
|
|
* on the other fields of |mode|. If neither |truncate| nor
|
|
* |create| is specified, the file is opened for appending.
|
|
*
|
|
* If neither |truncate|, |create| or |write| is specified, the file
|
|
* is opened for reading.
|
|
*
|
|
* Note that |false|, |null| or |undefined| flags are simply ignored.
|
|
*
|
|
* @param {*=} options Additional options for file opening. This
|
|
* implementation interprets the following fields:
|
|
*
|
|
* - {number} winShare If specified, a share mode, as per
|
|
* Windows function |CreateFile|. You can build it from
|
|
* constants |OS.Constants.Win.FILE_SHARE_*|. If unspecified,
|
|
* the file uses the default sharing policy: it can be opened
|
|
* for reading and/or writing and it can be removed by other
|
|
* processes and by the same process.
|
|
* - {number} winSecurity If specified, Windows security
|
|
* attributes, as per Windows function |CreateFile|. If unspecified,
|
|
* no security attributes.
|
|
* - {number} winAccess If specified, Windows access mode, as
|
|
* per Windows function |CreateFile|. This also requires option
|
|
* |winDisposition| and this replaces argument |mode|. If unspecified,
|
|
* uses the string |mode|.
|
|
* - {number} winDisposition If specified, Windows disposition mode,
|
|
* as per Windows function |CreateFile|. This also requires option
|
|
* |winAccess| and this replaces argument |mode|. If unspecified,
|
|
* uses the string |mode|.
|
|
*
|
|
* @return {File} A file object.
|
|
* @throws {OS.File.Error} If the file could not be opened.
|
|
*/
|
|
File.open = function Win_open(path, mode = noOptions, options = noOptions) {
|
|
let share = options.winShare || DEFAULT_SHARE;
|
|
let security = options.winSecurity || null;
|
|
let flags = options.winFlags || DEFAULT_FLAGS;
|
|
let template = options.winTemplate?options.winTemplate._fd:null;
|
|
let access;
|
|
let disposition;
|
|
if ("winAccess" in options && "winDisposition" in options) {
|
|
access = options.winAccess;
|
|
disposition = options.winDisposition;
|
|
} else if (("winAccess" in options && !("winDisposition" in options))
|
|
||(!("winAccess" in options) && "winDisposition" in options)) {
|
|
throw new TypeError("OS.File.open requires either both options " +
|
|
"winAccess and winDisposition or neither");
|
|
} else {
|
|
mode = OS.Shared.AbstractFile.normalizeOpenMode(mode);
|
|
if (mode.read) {
|
|
access |= Const.GENERIC_READ;
|
|
}
|
|
if (mode.write) {
|
|
access |= Const.GENERIC_WRITE;
|
|
}
|
|
// Finally, handle create/existing/trunc
|
|
if (mode.trunc) {
|
|
if (mode.existing) {
|
|
// It seems that Const.TRUNCATE_EXISTING is broken
|
|
// in presence of links (source, anyone?). We need
|
|
// to open normally, then perform truncation manually.
|
|
disposition = Const.OPEN_EXISTING;
|
|
} else {
|
|
disposition = Const.CREATE_ALWAYS;
|
|
}
|
|
} else if (mode.create) {
|
|
disposition = Const.CREATE_NEW;
|
|
} else if (mode.read && !mode.write) {
|
|
disposition = Const.OPEN_EXISTING;
|
|
} else /*append*/ {
|
|
if (mode.existing) {
|
|
disposition = Const.OPEN_EXISTING;
|
|
} else {
|
|
disposition = Const.OPEN_ALWAYS;
|
|
}
|
|
}
|
|
}
|
|
let file = error_or_file(WinFile.CreateFile(path,
|
|
access, share, security, disposition, flags, template));
|
|
if (!(mode.trunc && mode.existing)) {
|
|
return file;
|
|
}
|
|
// Now, perform manual truncation
|
|
file.setPosition(0, File.POS_START);
|
|
throw_on_zero("open",
|
|
WinFile.SetEndOfFile(file.fd));
|
|
return file;
|
|
};
|
|
|
|
/**
|
|
* Checks if a file or directory exists
|
|
*
|
|
* @param {string} path The path to the file.
|
|
*
|
|
* @return {bool} true if the file exists, false otherwise.
|
|
*/
|
|
File.exists = function Win_exists(path) {
|
|
try {
|
|
let file = File.open(path, FILE_STAT_MODE, FILE_STAT_OPTIONS);
|
|
file.close();
|
|
return true;
|
|
} catch (x) {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Remove an existing file.
|
|
*
|
|
* @param {string} path The name of the file.
|
|
* @throws {OS.File.Error} In case of I/O error.
|
|
*/
|
|
File.remove = function remove(path) {
|
|
throw_on_zero("remove",
|
|
WinFile.DeleteFile(path));
|
|
};
|
|
|
|
/**
|
|
* Remove an empty directory.
|
|
*
|
|
* @param {string} path The name of the directory to remove.
|
|
* @param {*=} options Additional options.
|
|
* - {bool} ignoreAbsent If |true|, do not fail if the
|
|
* directory does not exist yet.
|
|
*/
|
|
File.removeEmptyDir = function removeEmptyDir(path, options = noOptions) {
|
|
let result = WinFile.RemoveDirectory(path);
|
|
if (!result) {
|
|
if (options.ignoreAbsent &&
|
|
ctypes.winLastError == Const.ERROR_FILE_NOT_FOUND) {
|
|
return;
|
|
}
|
|
throw new File.Error("removeEmptyDir");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create a directory.
|
|
*
|
|
* @param {string} path The name of the directory.
|
|
* @param {*=} options Additional options. This
|
|
* implementation interprets the following fields:
|
|
*
|
|
* - {C pointer} winSecurity If specified, security attributes
|
|
* as per winapi function |CreateDirectory|. If unspecified,
|
|
* use the default security descriptor, inherited from the
|
|
* parent directory.
|
|
* - {bool} ignoreExisting If |true|, do not fail if the
|
|
* directory already exists.
|
|
*/
|
|
File.makeDir = function makeDir(path, options = noOptions) {
|
|
let security = options.winSecurity || null;
|
|
let result = WinFile.CreateDirectory(path, security);
|
|
if (result ||
|
|
options.ignoreExisting &&
|
|
ctypes.winLastError == OS.Constants.Win.ERROR_ALREADY_EXISTS) {
|
|
return;
|
|
}
|
|
throw new File.Error("makeDir");
|
|
};
|
|
|
|
/**
|
|
* Copy a file to a destination.
|
|
*
|
|
* @param {string} sourcePath The platform-specific path at which
|
|
* the file may currently be found.
|
|
* @param {string} destPath The platform-specific path at which the
|
|
* file should be copied.
|
|
* @param {*=} options An object which may contain the following fields:
|
|
*
|
|
* @option {bool} noOverwrite - If true, this function will fail if
|
|
* a file already exists at |destPath|. Otherwise, if this file exists,
|
|
* it will be erased silently.
|
|
*
|
|
* @throws {OS.File.Error} In case of any error.
|
|
*
|
|
* General note: The behavior of this function is defined only when
|
|
* it is called on a single file. If it is called on a directory, the
|
|
* behavior is undefined and may not be the same across all platforms.
|
|
*
|
|
* General note: The behavior of this function with respect to metadata
|
|
* is unspecified. Metadata may or may not be copied with the file. The
|
|
* behavior may not be the same across all platforms.
|
|
*/
|
|
File.copy = function copy(sourcePath, destPath, options = noOptions) {
|
|
throw_on_zero("copy",
|
|
WinFile.CopyFile(sourcePath, destPath, options.noOverwrite || false)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Move a file to a destination.
|
|
*
|
|
* @param {string} sourcePath The platform-specific path at which
|
|
* the file may currently be found.
|
|
* @param {string} destPath The platform-specific path at which the
|
|
* file should be moved.
|
|
* @param {*=} options An object which may contain the following fields:
|
|
*
|
|
* @option {bool} noOverwrite - If set, this function will fail if
|
|
* a file already exists at |destPath|. Otherwise, if this file exists,
|
|
* it will be erased silently.
|
|
* @option {bool} noCopy - If set, this function will fail if the
|
|
* operation is more sophisticated than a simple renaming, i.e. if
|
|
* |sourcePath| and |destPath| are not situated on the same drive.
|
|
*
|
|
* @throws {OS.File.Error} In case of any error.
|
|
*
|
|
* General note: The behavior of this function is defined only when
|
|
* it is called on a single file. If it is called on a directory, the
|
|
* behavior is undefined and may not be the same across all platforms.
|
|
*
|
|
* General note: The behavior of this function with respect to metadata
|
|
* is unspecified. Metadata may or may not be moved with the file. The
|
|
* behavior may not be the same across all platforms.
|
|
*/
|
|
File.move = function move(sourcePath, destPath, options = noOptions) {
|
|
let flags = 0;
|
|
if (!options.noCopy) {
|
|
flags = Const.MOVEFILE_COPY_ALLOWED;
|
|
}
|
|
if (!options.noOverwrite) {
|
|
flags = flags | Const.MOVEFILE_REPLACE_EXISTING;
|
|
}
|
|
throw_on_zero("move",
|
|
WinFile.MoveFileEx(sourcePath, destPath, flags)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* A global value used to receive data during time conversions.
|
|
*/
|
|
let gSystemTime = new OS.Shared.Type.SystemTime.implementation();
|
|
let gSystemTimePtr = gSystemTime.address();
|
|
|
|
/**
|
|
* Utility function: convert a FILETIME to a JavaScript Date.
|
|
*/
|
|
let FILETIME_to_Date = function FILETIME_to_Date(fileTime) {
|
|
if (fileTime == null) {
|
|
throw new TypeError("Expecting a non-null filetime");
|
|
}
|
|
throw_on_zero("FILETIME_to_Date",
|
|
WinFile.FileTimeToSystemTime(fileTime.address(),
|
|
gSystemTimePtr));
|
|
// Windows counts hours, minutes, seconds from UTC,
|
|
// JS counts from local time, so we need to go through UTC.
|
|
let utc = Date.UTC(gSystemTime.wYear,
|
|
gSystemTime.wMonth - 1
|
|
/*Windows counts months from 1, JS from 0*/,
|
|
gSystemTime.wDay, gSystemTime.wHour,
|
|
gSystemTime.wMinute, gSystemTime.wSecond,
|
|
gSystemTime.wMilliSeconds);
|
|
return new Date(utc);
|
|
};
|
|
|
|
/**
|
|
* Iterate on one directory.
|
|
*
|
|
* This iterator will not enter subdirectories.
|
|
*
|
|
* @param {string} path The directory upon which to iterate.
|
|
* @param {*=} options Ignored in this implementation.
|
|
*
|
|
* @throws {File.Error} If |path| does not represent a directory or
|
|
* if the directory cannot be iterated.
|
|
* @constructor
|
|
*/
|
|
File.DirectoryIterator = function DirectoryIterator(path, options) {
|
|
exports.OS.Shared.AbstractFile.AbstractIterator.call(this);
|
|
if (options && options.winPattern) {
|
|
this._pattern = path + "\\" + options.winPattern;
|
|
} else {
|
|
this._pattern = path + "\\*";
|
|
}
|
|
this._path = path;
|
|
|
|
// Pre-open the first item.
|
|
this._first = true;
|
|
this._findData = new OS.Shared.Type.FindData.implementation();
|
|
this._findDataPtr = this._findData.address();
|
|
this._handle = WinFile.FindFirstFile(this._pattern, this._findDataPtr);
|
|
if (this._handle == Const.INVALID_HANDLE_VALUE) {
|
|
let error = ctypes.winLastError;
|
|
this._findData = null;
|
|
this._findDataPtr = null;
|
|
if (error == Const.ERROR_FILE_NOT_FOUND) {
|
|
// Directory is empty, let's behave as if it were closed
|
|
LOG("Directory is empty");
|
|
this._closed = true;
|
|
this._exists = true;
|
|
} else if (error == Const.ERROR_PATH_NOT_FOUND) {
|
|
// Directory does not exist, let's throw if we attempt to walk it
|
|
LOG("Directory does not exist");
|
|
this._closed = true;
|
|
this._exists = false;
|
|
} else {
|
|
throw new File.Error("DirectoryIterator", error);
|
|
}
|
|
} else {
|
|
this._closed = false;
|
|
this._exists = true;
|
|
}
|
|
};
|
|
|
|
File.DirectoryIterator.prototype = Object.create(exports.OS.Shared.AbstractFile.AbstractIterator.prototype);
|
|
|
|
|
|
/**
|
|
* Fetch the next entry in the directory.
|
|
*
|
|
* @return null If we have reached the end of the directory.
|
|
*/
|
|
File.DirectoryIterator.prototype._next = function _next() {
|
|
// Bailout if the directory does not exist
|
|
if (!this._exists) {
|
|
throw File.Error.noSuchFile("DirectoryIterator.prototype.next");
|
|
}
|
|
// Bailout if the iterator is closed.
|
|
if (this._closed) {
|
|
return null;
|
|
}
|
|
// If this is the first entry, we have obtained it already
|
|
// during construction.
|
|
if (this._first) {
|
|
this._first = false;
|
|
return this._findData;
|
|
}
|
|
|
|
if (WinFile.FindNextFile(this._handle, this._findDataPtr)) {
|
|
return this._findData;
|
|
} else {
|
|
let error = ctypes.winLastError;
|
|
this.close();
|
|
if (error == Const.ERROR_NO_MORE_FILES) {
|
|
return null;
|
|
} else {
|
|
throw new File.Error("iter (FindNextFile)", error);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Return the next entry in the directory, if any such entry is
|
|
* available.
|
|
*
|
|
* Skip special directories "." and "..".
|
|
*
|
|
* @return {File.Entry} The next entry in the directory.
|
|
* @throws {StopIteration} Once all files in the directory have been
|
|
* encountered.
|
|
*/
|
|
File.DirectoryIterator.prototype.next = function next() {
|
|
// FIXME: If we start supporting "\\?\"-prefixed paths, do not forget
|
|
// that "." and ".." are absolutely normal file names if _path starts
|
|
// with such prefix
|
|
for (let entry = this._next(); entry != null; entry = this._next()) {
|
|
let name = entry.cFileName.readString();
|
|
if (name == "." || name == "..") {
|
|
continue;
|
|
}
|
|
return new File.DirectoryIterator.Entry(entry, this._path);
|
|
}
|
|
throw StopIteration;
|
|
};
|
|
|
|
File.DirectoryIterator.prototype.close = function close() {
|
|
if (this._closed) {
|
|
return;
|
|
}
|
|
this._closed = true;
|
|
if (this._handle) {
|
|
// We might not have a handle if the iterator is closed
|
|
// before being used.
|
|
throw_on_zero("FindClose",
|
|
WinFile.FindClose(this._handle));
|
|
this._handle = null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Determine whether the directory exists.
|
|
*
|
|
* @return {boolean}
|
|
*/
|
|
File.DirectoryIterator.prototype.exists = function exists() {
|
|
return this._exists;
|
|
};
|
|
|
|
File.DirectoryIterator.Entry = function Entry(win_entry, parent) {
|
|
if (!win_entry.dwFileAttributes || !win_entry.ftCreationTime ||
|
|
!win_entry.ftLastAccessTime || !win_entry.ftLastWriteTime)
|
|
throw new TypeError();
|
|
|
|
// Copy the relevant part of |win_entry| to ensure that
|
|
// our data is not overwritten prematurely.
|
|
let isDir = !!(win_entry.dwFileAttributes & Const.FILE_ATTRIBUTE_DIRECTORY);
|
|
let isSymLink = !!(win_entry.dwFileAttributes & Const.FILE_ATTRIBUTE_REPARSE_POINT);
|
|
|
|
let winCreationDate = FILETIME_to_Date(win_entry.ftCreationTime);
|
|
let winLastWriteDate = FILETIME_to_Date(win_entry.ftLastWriteTime);
|
|
let winLastAccessDate = FILETIME_to_Date(win_entry.ftLastAccessTime);
|
|
|
|
let name = win_entry.cFileName.readString();
|
|
if (!name) {
|
|
throw new TypeError("Empty name");
|
|
}
|
|
|
|
if (!parent) {
|
|
throw new TypeError("Empty parent");
|
|
}
|
|
this._parent = parent;
|
|
|
|
let path = OS.Win.Path.join(this._parent, name);
|
|
|
|
exports.OS.Shared.Win.AbstractEntry.call(this, isDir, isSymLink, name,
|
|
winCreationDate, winLastWriteDate,
|
|
winLastAccessDate, path);
|
|
};
|
|
File.DirectoryIterator.Entry.prototype = Object.create(exports.OS.Shared.Win.AbstractEntry.prototype);
|
|
|
|
/**
|
|
* Return a version of an instance of
|
|
* File.DirectoryIterator.Entry that can be sent from a worker
|
|
* thread to the main thread. Note that deserialization is
|
|
* asymmetric and returns an object with a different
|
|
* implementation.
|
|
*/
|
|
File.DirectoryIterator.Entry.toMsg = function toMsg(value) {
|
|
if (!value instanceof File.DirectoryIterator.Entry) {
|
|
throw new TypeError("parameter of " +
|
|
"File.DirectoryIterator.Entry.toMsg must be a " +
|
|
"File.DirectoryIterator.Entry");
|
|
}
|
|
let serialized = {};
|
|
for (let key in File.DirectoryIterator.Entry.prototype) {
|
|
serialized[key] = value[key];
|
|
}
|
|
return serialized;
|
|
};
|
|
|
|
|
|
/**
|
|
* Information on a file.
|
|
*
|
|
* To obtain the latest information on a file, use |File.stat|
|
|
* (for an unopened file) or |File.prototype.stat| (for an
|
|
* already opened file).
|
|
*
|
|
* @constructor
|
|
*/
|
|
File.Info = function Info(stat) {
|
|
let isDir = !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_DIRECTORY);
|
|
let isSymLink = !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_REPARSE_POINT);
|
|
|
|
let winBirthDate = FILETIME_to_Date(stat.ftCreationTime);
|
|
let lastAccessDate = FILETIME_to_Date(stat.ftLastAccessTime);
|
|
|
|
let value = ctypes.UInt64.join(stat.nFileSizeHigh, stat.nFileSizeLow);
|
|
let size = exports.OS.Shared.Type.uint64_t.importFromC(value);
|
|
|
|
exports.OS.Shared.Win.AbstractInfo.call(this, isDir, isSymLink, size,
|
|
winBirthDate, lastAccessDate);
|
|
};
|
|
File.Info.prototype = Object.create(exports.OS.Shared.Win.AbstractInfo.prototype);
|
|
|
|
/**
|
|
* Return a version of an instance of File.Info that can be sent
|
|
* from a worker thread to the main thread. Note that deserialization
|
|
* is asymmetric and returns an object with a different implementation.
|
|
*/
|
|
File.Info.toMsg = function toMsg(stat) {
|
|
if (!stat instanceof File.Info) {
|
|
throw new TypeError("parameter of File.Info.toMsg must be a File.Info");
|
|
}
|
|
let serialized = {};
|
|
for (let key in File.Info.prototype) {
|
|
serialized[key] = stat[key];
|
|
}
|
|
return serialized;
|
|
};
|
|
|
|
|
|
/**
|
|
* Fetch the information on a file.
|
|
*
|
|
* Performance note: if you have opened the file already,
|
|
* method |File.prototype.stat| is generally much faster
|
|
* than method |File.stat|.
|
|
*
|
|
* Platform-specific note: under Windows, if the file is
|
|
* already opened without sharing of the read capability,
|
|
* this function will fail.
|
|
*
|
|
* @return {File.Information}
|
|
*/
|
|
File.stat = function stat(path) {
|
|
let file = File.open(path, FILE_STAT_MODE, FILE_STAT_OPTIONS);
|
|
try {
|
|
return file.stat();
|
|
} finally {
|
|
file.close();
|
|
}
|
|
};
|
|
// All of the following is required to ensure that File.stat
|
|
// also works on directories.
|
|
const FILE_STAT_MODE = {
|
|
read:true
|
|
};
|
|
const FILE_STAT_OPTIONS = {
|
|
// Directories can be opened neither for reading(!) nor for writing
|
|
winAccess: 0,
|
|
// Directories can only be opened with backup semantics(!)
|
|
winFlags: OS.Constants.Win.FILE_FLAG_BACKUP_SEMANTICS,
|
|
winDisposition: OS.Constants.Win.OPEN_EXISTING
|
|
};
|
|
|
|
File.read = exports.OS.Shared.AbstractFile.read;
|
|
File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
|
|
|
|
/**
|
|
* Get the current directory by getCurrentDirectory.
|
|
*/
|
|
File.getCurrentDirectory = function getCurrentDirectory() {
|
|
// This function is more complicated than one could hope.
|
|
//
|
|
// This is due to two facts:
|
|
// - the maximal length of a path under Windows is not completely
|
|
// specified (there is a constant MAX_PATH, but it is quite possible
|
|
// to create paths that are much larger, see bug 744413);
|
|
// - if we attempt to call |GetCurrentDirectory| with a buffer that
|
|
// is too short, it returns the length of the current directory, but
|
|
// this length might be insufficient by the time we can call again
|
|
// the function with a larger buffer, in the (unlikely byt possible)
|
|
// case in which the process changes directory to a directory with
|
|
// a longer name between both calls.
|
|
let buffer_size = 4096;
|
|
while (true) {
|
|
let array = new (ctypes.ArrayType(ctypes.jschar, buffer_size))();
|
|
let expected_size = throw_on_zero("getCurrentDirectory",
|
|
WinFile.GetCurrentDirectory(buffer_size, array)
|
|
);
|
|
if (expected_size <= buffer_size) {
|
|
return array.readString();
|
|
}
|
|
// At this point, we are in a case in which our buffer was not
|
|
// large enough to hold the name of the current directory.
|
|
// Consequently
|
|
|
|
// Note that, even in crazy scenarios, the loop will eventually
|
|
// converge, as the length of the paths cannot increase infinitely.
|
|
buffer_size = expected_size;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Set the current directory by setCurrentDirectory.
|
|
*/
|
|
File.setCurrentDirectory = function setCurrentDirectory(path) {
|
|
throw_on_zero("setCurrentDirectory",
|
|
WinFile.SetCurrentDirectory(path));
|
|
};
|
|
|
|
/**
|
|
* Get/set the current directory by |curDir|.
|
|
*/
|
|
Object.defineProperty(File, "curDir", {
|
|
set: function(path) {
|
|
this.setCurrentDirectory(path);
|
|
},
|
|
get: function() {
|
|
return this.getCurrentDirectory();
|
|
}
|
|
}
|
|
);
|
|
|
|
// Utility functions, used for error-handling
|
|
function error_or_file(maybe) {
|
|
if (maybe == exports.OS.Constants.Win.INVALID_HANDLE_VALUE) {
|
|
throw new File.Error("open");
|
|
}
|
|
return new File(maybe);
|
|
}
|
|
function throw_on_zero(operation, result) {
|
|
if (result == 0) {
|
|
throw new File.Error(operation);
|
|
}
|
|
return result;
|
|
}
|
|
function throw_on_negative(operation, result) {
|
|
if (result < 0) {
|
|
throw new File.Error(operation);
|
|
}
|
|
return result;
|
|
}
|
|
function throw_on_null(operation, result) {
|
|
if (result == null || (result.isNull && result.isNull())) {
|
|
throw new File.Error(operation);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
File.Win = exports.OS.Win.File;
|
|
File.Error = exports.OS.Shared.Win.Error;
|
|
exports.OS.File = File;
|
|
|
|
exports.OS.Path = exports.OS.Win.Path;
|
|
|
|
Object.defineProperty(File, "POS_START", { value: OS.Shared.POS_START });
|
|
Object.defineProperty(File, "POS_CURRENT", { value: OS.Shared.POS_CURRENT });
|
|
Object.defineProperty(File, "POS_END", { value: OS.Shared.POS_END });
|
|
})(this);
|
|
}
|