184
edits
m (→Opening/creating: optional flags) |
No edit summary |
||
(24 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
This page details a proposal for [https://bugzilla.mozilla.org/show_bug.cgi?id=563742 | This page details a proposal for [https://bugzilla.mozilla.org/show_bug.cgi?id=563742 Bug 563742 - Efficient ctypes API for file handling]. | ||
'''Note''' This is an early draft. The final API is documented [https://developer.mozilla.org/en/JavaScript_OS.File on MDN]. | |||
The general idea of this API is to provide a low-level, cross-platform, fast access to file management functions. For this reason, it does not implement some primitives that are very different between platforms, e.g. chmod, mmap, epoll. | The general idea of this API is to provide a low-level, cross-platform, fast access to file management functions. For this reason, it does not implement some primitives that are very different between platforms, e.g. chmod, mmap, epoll. | ||
''Conventions'' | '''Conventions''' This document uses the conventions of [http://code.google.com/closure/compiler/docs/js-for-compiler.html the Google Closure Compiler] for type annotations. | ||
= Problems addressed by this API = | = Problems addressed by this API = | ||
The current API for file management is based on <tt>nsIFile</tt> and its various <tt>nsLocalFile*</tt> implementations. Several performance-related concerns have surfaced wrt the <tt>nsIFile</tt> API: | The current API for file management is based on <tt>nsIFile</tt> and its various <tt>nsLocalFile*</tt> implementations. Several performance-related concerns have surfaced wrt the <tt>nsIFile</tt> API: | ||
Note | *the internal implementation is based on <tt>string</tt> paths, rather than file descriptors, which in turn causes repeated filename lookups during the execution of any non-trivial sequence of operations; | ||
*it causes numerous expensive calls to <tt>stat</tt>, both when traversing a directory and when a user is interested in several properties of a file; | |||
*input/output streams do not play too nicely with JavaScript. | |||
'''Note''' Do we know how expensive the XPCom/XPConnect overhead is, by opposition to js-ctypes and JSAPI? | |||
The present proposal attempts to address these points as follows: | The present proposal attempts to address these points as follows: | ||
*it attempts to minimize filename lookups by using file/directory descriptors wherever possible; | |||
*it attempts to minimize calls to <tt>stat</tt>, in particular when traversing a directory; | |||
*input/output attempts to play much more nicely with JavaScript, by using JavaScript low-level data structures designed for this task (and which didn't exist when the <tt>nsIFile</tt> API was designed). | |||
= Problems not addressed by this API = | |||
This API is synchronous. This API does not attempt to solve any asynchronicity issue. Consequently, this API is not meant to be used in the main thread. See [https://bugzilla.mozilla.org/show_bug.cgi?id=691309|bug 691309] for one discussion (among many) on how to remove IO from the main thread. | |||
= Module | Note that it is quite possible to build an asynchronous file API based on this API. This is quite simple, and the process of building an async API on top of a sync API may be the best way to obtain within a finite time a cross-platform asynchronous API that works. | ||
= Module <tt>Files</tt> = | |||
This module is the first access point to the file API. It contains constructors, functions to copy, move files, etc. as well as the constants used in the API. | This module is the first access point to the file API. It contains constructors, functions to copy, move files, etc. as well as the constants used in the API. | ||
Line 29: | Line 38: | ||
== Access a file or a directory == | == Access a file or a directory == | ||
/** | /** | ||
* Create a temporary directory. | * Create a temporary file in the system-defined temporary directory (if it exists). This file is deleted when the process closes or when the file is closed, whichever happens first. | ||
* | * | ||
* @param {string=} name An optional name that can be used for debugging purposes as a template for the name of the file created. | |||
*/ | |||
* @ | createTempFile: function(name) { | ||
*/ | //Unix: uses cached [nsIDirectoryService] to get temporary directoy, [mkstemp] and [this.openFile] | ||
//Windows: uses cached [nsIDirectoryService] to get temporary directory, [GetTempFileName] + [CreateFile] http://msdn.microsoft.com/en-us/library/windows/desktop/aa363875%28v=vs.85%29.aspx | |||
//Unix: | |||
//Windows: [ | |||
}, | }, | ||
== General utilities == | == General utilities == | ||
/** | /** | ||
* Copy a file | * Copy a file. | ||
* | * | ||
* Note: OS-accelerated on some platforms. | * Note: OS-accelerated on some platforms. | ||
Line 55: | Line 62: | ||
* @param {string} target The name of the file/directory to be created. | * @param {string} target The name of the file/directory to be created. | ||
* @param {boolean} overwrite If [false] and if the target already exists, fail. | * @param {boolean} overwrite If [false] and if the target already exists, fail. | ||
* @throws | * @throws RawFileError | ||
*/ | */ | ||
copy: function(source, target, overwrite) | copy: function(source, target, overwrite) | ||
Line 75: | Line 82: | ||
* @param {string} target The name of the file/directory to be created. | * @param {string} target The name of the file/directory to be created. | ||
* @param {boolean} overwrite If [false] and if the target already exists, fail. | * @param {boolean} overwrite If [false] and if the target already exists, fail. | ||
* @throws | * @throws RawFileError | ||
*/ | */ | ||
move: function(source, target, overwrite) | move: function(source, target, overwrite) | ||
{ | { | ||
//Unix: maps to [rename] or, when [rename] returns EXDEV, on [ | //Unix: maps to [rename] or, when [rename] returns EXDEV, on [Files.copy]+[Files.remove] | ||
//Windows: maps to [MoveFile] http://msdn.microsoft.com/en-us/library/aa365239(v=VS.85).aspx | //Windows: maps to [MoveFile] http://msdn.microsoft.com/en-us/library/aa365239(v=VS.85).aspx | ||
//Check if it works with directories | //Check if it works with directories | ||
Line 85: | Line 92: | ||
/** | /** | ||
* Remove a file | * Remove a file. | ||
* | * | ||
* Note: OS-accelerated under some platforms. | * Note: OS-accelerated under some platforms. | ||
Line 99: | Line 106: | ||
/** | /** | ||
* Return the location of the directory/folder used to store | * Return the location of the directory/folder used to store user profile | ||
* | * | ||
* Computed lazily, cached. | * Computed lazily, cached. | ||
* | * | ||
* @return { | * @return {RawDir} | ||
*/ | */ | ||
get | get profileDir() { | ||
//All platforms: use [nsIDirectoryService | //All platforms: use [nsIDirectoryService] to get the directory the first time, cache it. | ||
}, | }, | ||
//TODO: Add other well-known directories. | //TODO: Add other well-known directories. | ||
== Flags == | == Flags == | ||
Line 150: | Line 153: | ||
*/ | */ | ||
Pragma: { | Pragma: { | ||
/** Windows-specific pragma: optimize cache for sequential access*/ | /** Windows-specific pragma: optimize cache for sequential access*/ | ||
Line 191: | Line 192: | ||
== Interfaces == | == Interfaces == | ||
=== File Information === | |||
/** | /** | ||
* The kind of information that can be found by calling [ | * The kind of information that can be found by calling [RawFile.info] or [RawDir.contents]. | ||
* | * | ||
* Note that some or all fields may be computed lazily. | * Note that some or all fields may be computed lazily. | ||
Line 217: | Line 219: | ||
}, | }, | ||
/** | /** | ||
* Note: this property is OS-accelerated for entries returned by [forEachFile] or by enumerating files in a directory. | * Note: this property is OS-accelerated for entries returned by [forEachFile] or by enumerating files in a directory. | ||
Line 234: | Line 228: | ||
} | } | ||
}, | }, | ||
=== Directory entries === | |||
DirEntry: { | |||
/** | |||
* The name of the file. | |||
* | |||
* Note that there is no guarantee that the file still exists by the time you attempt to open it. | |||
* | |||
* @return {string=} | |||
*/ | |||
get name(): { ... | |||
}, | |||
/** | |||
* All the information that could be gathered about the file without opening it. | |||
* | |||
* @return {FileInfo} | |||
*/ | |||
get info(): { ... | |||
//Precomputed by [RawDir.contents] | |||
} | |||
}, | |||
=== Errors === | |||
/** | /** | ||
* An exception launched by this module. | * An exception launched by this module. | ||
Line 245: | Line 262: | ||
Error: function(){} | Error: function(){} | ||
= Instances of | = Instances of <tt>RawFile</tt> = | ||
A <tt> | A <tt>RawFile</tt> is a low-level object wrapping a native file descriptor (under variants of Unix) or a file handle (under Windows). | ||
== Reading == | == Reading == | ||
Line 256: | Line 273: | ||
* @param {ArrayBuffer} buf The buffer which will receive the data. | * @param {ArrayBuffer} buf The buffer which will receive the data. | ||
* @param {number} offset The position in the array at which to start putting data, in bytes. | * @param {number} offset The position in the array at which to start putting data, in bytes. | ||
* @param {number} size The maximal number of bytes to read. This method can read less bytes if the file is shorter. | * @param {number} size The maximal number of bytes to read. This method can read less bytes if (and only if) the file is shorter. | ||
* @return {number} The number of bytes read. | * @return {number} The number of bytes read. | ||
* @throws { | * @throws {RawFileException} In case of file error. | ||
* @throws {INDEX_SIZE_ERR} In case of array error. | * @throws {INDEX_SIZE_ERR} In case of array error. | ||
*/ | */ | ||
Line 265: | Line 282: | ||
//Windows: [ReadFile] http://msdn.microsoft.com/en-us/library/windows/desktop/aa365467%28v=VS.85%29.aspx | //Windows: [ReadFile] http://msdn.microsoft.com/en-us/library/windows/desktop/aa365467%28v=VS.85%29.aspx | ||
}, | }, | ||
/** | /** | ||
* As [read], but read from a given position and do not advance. | * As [read], but read from a given position and do not advance. | ||
Line 274: | Line 291: | ||
* @param {number} size The maximal number of bytes to read. This method can read less bytes if the file is shorter. | * @param {number} size The maximal number of bytes to read. This method can read less bytes if the file is shorter. | ||
* @return {number} The number of bytes read. | * @return {number} The number of bytes read. | ||
* @throws { | * @throws {RawFileException} In case of file error. | ||
* @throws {INDEX_SIZE_ERR} In case of array error. | * @throws {INDEX_SIZE_ERR} In case of array error. | ||
*/ | */ | ||
Line 288: | Line 305: | ||
* | * | ||
* @param {ArrayBuffer} buf The buffer containing the data. | * @param {ArrayBuffer} buf The buffer containing the data. | ||
* @param {number} offset The position in the array at which the data starts, in bytes. | * @param {number=} offset The position in the array at which the data starts, in bytes. If unspecified, 0. | ||
* @param {number} size The maximal number of bytes to read. This method can write less bytes, depending on buffering. | * @param {number=} size The maximal number of bytes to read. This method can write less bytes, depending on buffering. If unspecified, everything from [offset]. | ||
* | * | ||
* @return {number} The number of bytes written. | * @return {number} The number of bytes written. | ||
* @throws { | * @throws {RawFileException} In case of file error. | ||
* @throws {INDEX_SIZE_ERR} In case of array error. | * @throws {INDEX_SIZE_ERR} In case of array error. | ||
*/ | */ | ||
Line 313: | Line 330: | ||
* Gather information about the file | * Gather information about the file | ||
* | * | ||
* @return { | * @return {Files.FileInfo} information about the file. | ||
*/ | */ | ||
stat: function() { | stat: function() { | ||
Line 336: | Line 353: | ||
* | * | ||
* @param {number} delta Number of bytes. Can be positive or negative. | * @param {number} delta Number of bytes. Can be positive or negative. | ||
* @param { | * @param {RawFile.Seek.Methodmethod} Determine whether [delta] is to be taken from the start of the file, from the end or from the current position. | ||
*/ | */ | ||
seek: function(delta, method) { | seek: function(delta, method) { | ||
Line 360: | Line 377: | ||
//Windows: [FlushFileBuffers] http://msdn.microsoft.com/en-us/library/windows/desktop/aa364439(v=VS.85).aspx | //Windows: [FlushFileBuffers] http://msdn.microsoft.com/en-us/library/windows/desktop/aa364439(v=VS.85).aspx | ||
}, | }, | ||
A <tt> | = Instances of <tt>RawDir</tt> = | ||
A <tt>RawDir</tt> is an object wrapping either a directory ''name'' or a directory descriptor, depending on the platform. On the Unix side, some of the methods rely upon (or have to reimplement) systems that obey recent versions of Posix, with functions such as <tt>openat</tt>. | |||
== Opening/creating == | == Opening/creating == | ||
Line 371: | Line 388: | ||
* | * | ||
* @param {string} leafName The name of the file. | * @param {string} leafName The name of the file. | ||
* @param {number=} accessMode A or-ing of flags, as specified by [ | * @param {number=} accessMode A or-ing of flags, as specified by [RawFile.Open.Access]. If this argument is not provided, we assume 0, i.e. no flags. | ||
* @param {number=} contentMode A or-ing of flags, as specified by [ | * @param {number=} contentMode A or-ing of flags, as specified by [RawFile.Content.Access]. If this argument is not provided, we assume 0, i.e. no flags. | ||
* @param {number=} pragmaMode A or-ing of flags, as specified by [ | * @param {number=} pragmaMode A or-ing of flags, as specified by [RawFile.Pragma.Access]. If this argument is not provided, we assume 0, i.e. no flags. | ||
* @return { | * @return {RawFile} a RawFile | ||
* | * | ||
* @throws | * @throws RawFileError | ||
*/ | */ | ||
openFile: function(leafName, accessMode, contentMode, pragmaMode) { | openFile: function(leafName, accessMode, contentMode, pragmaMode) { | ||
//Linux: [openat] | //Linux: [openat] | ||
//Unix: decide between gnulib [openat] and simply [open] | //Unix: decide between gnulib [openat] and simply [open] | ||
//Windows: cf. [ | //Windows: cf. [RawFile.open] | ||
}, | }, | ||
/** | /** | ||
* Create a temporary file in this directory. This file is deleted when the process closes | * Create a temporary file in this directory. This file is deleted when the process closes or when the file is closed, whichever happens first. | ||
*/ | */ | ||
createTempFile: function() { | createTempFile: function() { | ||
Line 396: | Line 413: | ||
* | * | ||
* @param {string} leafName The platform-specific name of the directory. | * @param {string} leafName The platform-specific name of the directory. | ||
* @param {number=} accessMode A or-ing of flags, as specified by [ | * @param {number=} accessMode A or-ing of flags, as specified by [RawFile.OpenDir.Access] | ||
* | * | ||
* @returns { | * @returns {RawDir} a descriptor which may be used to access this directory | ||
*/ | */ | ||
openDirectory: function(leafName, accessMode) { | openDirectory: function(leafName, accessMode) { | ||
Line 406: | Line 423: | ||
/** | /** | ||
* Create a temporary | * Create a temporary subdirectory. | ||
* | * | ||
* Note: For the time being, there is no guarantee that the temporary directory will be cleaned | * Note: For the time being, there is no guarantee that the temporary directory will be cleaned | ||
* | * | ||
* @returns { | * @returns {RawDir} a descriptor which may be used to access this directory | ||
*/ | */ | ||
createTempDirectory: function() | createTempDirectory: function() | ||
Line 420: | Line 437: | ||
* Gather information about the directory | * Gather information about the directory | ||
* | * | ||
* @return { | * @return {Files.FileInfo} information about the file. | ||
*/ | */ | ||
stat: function() { | stat: function() { | ||
Line 428: | Line 445: | ||
== Browsing contents == | == Browsing contents == | ||
/** | /** | ||
* | * Get the contents of the directory. | ||
* | * | ||
* @param {string=} filter. If provided, uses OS-accelerated, platform-specific, filtering, where available. | * @param {string=} filter. If provided, uses OS-accelerated, platform-specific, filtering, where available. | ||
* @ | * @return {Array.<Files.DirEntry>} The list of files of the directory that match the filter. | ||
*/ | */ | ||
contents: function(filter) { | |||
//Unix: maps to [opendir], [dfd], [readdir]/[readdir64], lazy calls to [stat], lazy calls to [openat]/[open], [closedir] | //Unix: maps to [opendir], [dfd], [readdir]/[readdir64], lazy calls to [stat], lazy calls to [openat]/[open], [closedir] | ||
//Windows: maps to [FindFirstFile], [FindNextFile], [Close] | //Windows: maps to [FindFirstFile], [FindNextFile], [Close] | ||
Line 456: | Line 465: | ||
* linking -- very different between platforms | * linking -- very different between platforms | ||
* readString, writeString -- ArrayBuffer <-> String conversion most likely deserves its own API | * readString, writeString -- ArrayBuffer <-> String conversion most likely deserves its own API | ||
* opening a file or directory from a full path -- error-prone, difficult to optimize, favors hardcoding non-portable paths -- also, we intend to use this API mostly to access files in well-known directories. | * opening a file or directory from a full path -- error-prone, difficult to optimize, favors hardcoding non-portable paths -- also, we intend to use this API mostly to access files in well-known directories; | ||
* accessing the temporary directory -- it doesn't exist on Android, and emulating would require heavy scaffolding. | |||
= Implementation notes = | = Implementation notes = |
edits