diff --git a/src/filesystem/interface.js b/src/filesystem/interface.js index c8b6d30..824128b 100644 --- a/src/filesystem/interface.js +++ b/src/filesystem/interface.js @@ -1,38 +1,43 @@ -var { promisify } = require('es6-promisify'); +'use strict'; -var Path = require('../path.js'); -var nop = require('../shared.js').nop; +const { promisify } = require('es6-promisify'); -var Constants = require('../constants.js'); -var FILE_SYSTEM_NAME = Constants.FILE_SYSTEM_NAME; -var FS_FORMAT = Constants.FS_FORMAT; -var FS_READY = Constants.FS_READY; -var FS_PENDING = Constants.FS_PENDING; -var FS_ERROR = Constants.FS_ERROR; -var FS_NODUPEIDCHECK = Constants.FS_NODUPEIDCHECK; +const Path = require('../path.js'); -var providers = require('../providers/index.js'); +const providers = require('../providers/index.js'); -var Shell = require('../shell/shell.js'); -var Intercom = require('../../lib/intercom.js'); -var FSWatcher = require('../fs-watcher.js'); -var Errors = require('../errors.js'); -var defaultGuidFn = require('../shared.js').guid; +const Shell = require('../shell/shell.js'); +const Intercom = require('../../lib/intercom.js'); +const FSWatcher = require('../fs-watcher.js'); +const Errors = require('../errors.js'); +const { + nop, + guid: defaultGuidFn +} = require('../shared.js'); -var STDIN = Constants.STDIN; -var STDOUT = Constants.STDOUT; -var STDERR = Constants.STDERR; +const { + fsConstants, + FILE_SYSTEM_NAME, + FS_FORMAT, + FS_READY, + FS_PENDING, + FS_ERROR, + FS_NODUPEIDCHECK, + STDIN, + STDOUT, + STDERR +} = require('../constants.js'); // The core fs operations live on impl -var impl = require('./implementation.js'); +const impl = require('./implementation.js'); // node.js supports a calling pattern that leaves off a callback. function maybeCallback(callback) { - if(typeof callback === 'function') { + if (typeof callback === 'function') { return callback; } - return function(err) { - if(err) { + return function (err) { + if (err) { throw err; } }; @@ -40,7 +45,7 @@ function maybeCallback(callback) { // Default callback that logs an error if passed in function defaultCallback(err) { - if(err) { + if (err) { /* eslint no-console: 0 */ console.error('Filer error: ', err); } @@ -48,20 +53,20 @@ function defaultCallback(err) { // Get a path (String) from a file:// URL. Support URL() like objects // https://github.com/nodejs/node/blob/968e901aff38a343b1de4addebf79fd8fa991c59/lib/internal/url.js#L1381 function toPathIfFileURL(fileURLOrPath) { - if(!(fileURLOrPath && - fileURLOrPath.protocol && - fileURLOrPath.pathname)) { + if (!(fileURLOrPath && + fileURLOrPath.protocol && + fileURLOrPath.pathname)) { return fileURLOrPath; } - if(fileURLOrPath.protocol !== 'file:') { + if (fileURLOrPath.protocol !== 'file:') { throw new Errors.EINVAL('only file: URLs are supported for paths', fileURLOrPath); } - var pathname = fileURLOrPath.pathname; - for (var n = 0; n < pathname.length; n++) { + const pathname = fileURLOrPath.pathname; + for (let n = 0; n < pathname.length; n++) { if (pathname[n] === '%') { - var third = pathname.codePointAt(n + 2) | 0x20; + const third = pathname.codePointAt(n + 2) | 0x20; if (pathname[n + 1] === '2' && third === 102) { throw new Errors.EINVAL('file: URLs must not include encoded / characters', fileURLOrPath); } @@ -77,23 +82,23 @@ function toPathIfBuffer(bufferOrPath) { } function validatePath(path, allowRelative) { - if(!path) { + if (!path) { return new Errors.EINVAL('Path must be a string', path); - } else if(Path.isNull(path)) { + } else if (Path.isNull(path)) { return new Errors.EINVAL('Path must be a string without null bytes.', path); - } else if(!allowRelative && !Path.isAbsolute(path)) { + } else if (!allowRelative && !Path.isAbsolute(path)) { return new Errors.EINVAL('Path must be absolute.', path); } } function processPathArg(args, idx, allowRelative) { - var path = args[idx]; + let path = args[idx]; path = toPathIfFileURL(path); path = toPathIfBuffer(path); // Some methods specifically allow for rel paths (eg symlink with srcPath) - var err = validatePath(path, allowRelative); - if(err) { + let err = validatePath(path, allowRelative); + if (err) { throw err; } @@ -133,14 +138,14 @@ function FileSystem(options, callback) { options = options || {}; callback = callback || defaultCallback; - var flags = options.flags || []; - var guid = options.guid ? options.guid : defaultGuidFn; - var provider = options.provider || new providers.Default(options.name || FILE_SYSTEM_NAME); + const flags = options.flags || []; + const guid = options.guid ? options.guid : defaultGuidFn; + const provider = options.provider || new providers.Default(options.name || FILE_SYSTEM_NAME); // If we're given a provider, match its name unless we get an explicit name - var name = options.name || provider.name; - var forceFormatting = flags.includes(FS_FORMAT); + const name = options.name || provider.name; + const forceFormatting = flags.includes(FS_FORMAT); - var fs = this; + const fs = this; fs.readyState = FS_PENDING; fs.name = name; fs.error = null; @@ -150,24 +155,24 @@ function FileSystem(options, callback) { fs.stderr = STDERR; // Expose Node's fs.constants to users - fs.constants = Constants.fsConstants; + fs.constants = fsConstants; // Node also forwards the access mode flags onto fs - fs.F_OK = Constants.fsConstants.F_OK; - fs.R_OK = Constants.fsConstants.R_OK; - fs.W_OK = Constants.fsConstants.W_OK; - fs.X_OK = Constants.fsConstants.X_OK; + fs.F_OK = fsConstants.F_OK; + fs.R_OK = fsConstants.R_OK; + fs.W_OK = fsConstants.W_OK; + fs.X_OK = fsConstants.X_OK; // Expose Shell constructor this.Shell = Shell.bind(undefined, this); // Safely expose the operation queue - var queue = []; - this.queueOrRun = function(operation) { - var error; + let queue = []; + this.queueOrRun = function (operation) { + let error; - if(FS_READY === fs.readyState) { + if (FS_READY === fs.readyState) { operation.call(fs); - } else if(FS_ERROR === fs.readyState) { + } else if (FS_ERROR === fs.readyState) { error = new Errors.EFILESYSTEMERROR('unknown error'); } else { queue.push(operation); @@ -176,25 +181,25 @@ function FileSystem(options, callback) { return error; }; function runQueued() { - queue.forEach(function(operation) { + queue.forEach(function (operation) { operation.call(this); }.bind(fs)); queue = null; } // We support the optional `options` arg from node, but ignore it - this.watch = function(filename, options, listener) { - if(Path.isNull(filename)) { + this.watch = function (filename, options, listener) { + if (Path.isNull(filename)) { throw new Error('Path must be a string without null bytes.'); } - if(typeof options === 'function') { + if (typeof options === 'function') { listener = options; options = {}; } options = options || {}; listener = listener || nop; - var watcher = new FSWatcher(); + const watcher = new FSWatcher(); watcher.start(filename, false, options.recursive); watcher.on('change', listener); @@ -203,24 +208,24 @@ function FileSystem(options, callback) { // Deal with various approaches to node ID creation function wrappedGuidFn(context) { - return function(callback) { + return function (callback) { // Skip the duplicate ID check if asked to - if(flags.includes(FS_NODUPEIDCHECK)) { + if (flags.includes(FS_NODUPEIDCHECK)) { callback(null, guid()); return; } // Otherwise (default) make sure this id is unused first function guidWithCheck(callback) { - var id = guid(); - context.getObject(id, function(err, value) { - if(err) { + const id = guid(); + context.getObject(id, function (err, value) { + if (err) { callback(err); return; } // If this id is unused, use it, otherwise find another - if(!value) { + if (!value) { callback(null, id); } else { guidWithCheck(callback); @@ -234,28 +239,28 @@ function FileSystem(options, callback) { // Let other instances (in this or other windows) know about // any changes to this fs instance. function broadcastChanges(changes) { - if(!changes.length) { + if (!changes.length) { return; } - var intercom = Intercom.getInstance(); - changes.forEach(function(change) { + const intercom = Intercom.getInstance(); + changes.forEach(function (change) { intercom.emit(change.event, change.path); }); } // Open file system storage provider - provider.open(function(err) { + provider.open(function (err) { function complete(error) { function wrappedContext(methodName) { - var context = provider[methodName](); + let context = provider[methodName](); context.name = name; context.flags = flags; context.changes = []; context.guid = wrappedGuidFn(context); // When the context is finished, let the fs deal with any change events - context.close = function() { - var changes = context.changes; + context.close = function () { + let changes = context.changes; broadcastChanges(changes); changes.length = 0; }; @@ -268,15 +273,15 @@ function FileSystem(options, callback) { // for paths updated during the lifetime of the context). From this // point forward we won't call open again, so it's safe to drop it. fs.provider = { - openReadWriteContext: function() { + openReadWriteContext: function () { return wrappedContext('getReadWriteContext'); }, - openReadOnlyContext: function() { + openReadOnlyContext: function () { return wrappedContext('getReadOnlyContext'); } }; - if(error) { + if (error) { fs.readyState = FS_ERROR; } else { fs.readyState = FS_READY; @@ -285,18 +290,18 @@ function FileSystem(options, callback) { callback(error, fs); } - if(err) { + if (err) { return complete(err); } - var context = provider.getReadWriteContext(); + const context = provider.getReadWriteContext(); context.guid = wrappedGuidFn(context); // Mount the filesystem, formatting if necessary - if(forceFormatting) { + if (forceFormatting) { // Wipe the storage provider, then write root block - context.clear(function(err) { - if(err) { + context.clear(function (err) { + if (err) { return complete(err); } impl.ensureRootDirectory(context, complete); @@ -317,13 +322,13 @@ function FileSystem(options, callback) { * can be processed and validated before being passed on to the method. */ [ - { name: 'appendFile', promises: true, absPathArgs: [ 0 ] }, - { name: 'access', promises: true, absPathArgs: [ 0 ] }, - { name: 'chown', promises: true, absPathArgs: [ 0 ] }, - { name: 'chmod', promises: true, absPathArgs: [ 0 ] }, + { name: 'appendFile', promises: true, absPathArgs: [0] }, + { name: 'access', promises: true, absPathArgs: [0] }, + { name: 'chown', promises: true, absPathArgs: [0] }, + { name: 'chmod', promises: true, absPathArgs: [0] }, { name: 'close' }, // copyFile - https://github.com/filerjs/filer/issues/436 - { name: 'exists', absPathArgs: [ 0 ] }, + { name: 'exists', absPathArgs: [0] }, { name: 'fchown' }, { name: 'fchmod' }, // fdatasync - https://github.com/filerjs/filer/issues/653 @@ -334,64 +339,64 @@ function FileSystem(options, callback) { { name: 'fsync' }, { name: 'ftruncate' }, { name: 'futimes' }, - { name: 'getxattr', promises: true, absPathArgs: [ 0 ] }, + { name: 'getxattr', promises: true, absPathArgs: [0] }, // lchown - https://github.com/filerjs/filer/issues/620 // lchmod - https://github.com/filerjs/filer/issues/619 { name: 'link', promises: true, absPathArgs: [0, 1] }, { name: 'lseek' }, { name: 'lstat', promises: true }, - { name: 'mkdir', promises: true, absPathArgs: [ 0 ] }, + { name: 'mkdir', promises: true, absPathArgs: [0] }, { name: 'mkdtemp', promises: true }, - { name: 'mknod', promises: true, absPathArgs: [ 0 ] }, - { name: 'open', promises: true, absPathArgs: [ 0 ] }, - { name: 'readdir', promises: true, absPathArgs: [ 0 ] }, + { name: 'mknod', promises: true, absPathArgs: [0] }, + { name: 'open', promises: true, absPathArgs: [0] }, + { name: 'readdir', promises: true, absPathArgs: [0] }, { name: 'read' }, - { name: 'readFile', promises: true, absPathArgs: [ 0 ] }, - { name: 'readlink', promises: true, absPathArgs: [ 0 ] }, + { name: 'readFile', promises: true, absPathArgs: [0] }, + { name: 'readlink', promises: true, absPathArgs: [0] }, // realpath - https://github.com/filerjs/filer/issues/85 - { name: 'removexattr', promises: true, absPathArgs: [ 0 ] }, + { name: 'removexattr', promises: true, absPathArgs: [0] }, { name: 'rename', promises: true, absPathArgs: [0, 1] }, - { name: 'rmdir', promises: true, absPathArgs: [ 0 ] }, - { name: 'setxattr', promises: true, absPathArgs: [ 0 ] }, - { name: 'stat', promises: true, absPathArgs: [ 0 ] }, - { name: 'symlink', promises: true, relPathArgs: [ 0 ], absPathArgs: [ 1 ] }, - { name: 'truncate', promises: true, absPathArgs: [ 0 ] }, + { name: 'rmdir', promises: true, absPathArgs: [0] }, + { name: 'setxattr', promises: true, absPathArgs: [0] }, + { name: 'stat', promises: true, absPathArgs: [0] }, + { name: 'symlink', promises: true, relPathArgs: [0], absPathArgs: [1] }, + { name: 'truncate', promises: true, absPathArgs: [0] }, // unwatchFile - https://github.com/filerjs/filer/pull/553 - { name: 'unlink', promises: true, absPathArgs: [ 0 ] }, - { name: 'utimes', promises: true, absPathArgs: [ 0 ] }, + { name: 'unlink', promises: true, absPathArgs: [0] }, + { name: 'utimes', promises: true, absPathArgs: [0] }, // watch - implemented above in `this.watch` // watchFile - https://github.com/filerjs/filer/issues/654 - { name: 'writeFile', promises: true, absPathArgs: [ 0 ] }, + { name: 'writeFile', promises: true, absPathArgs: [0] }, { name: 'write' } - ].forEach(function(method) { - var methodName = method.name; - var shouldPromisify = method.promises === true; + ].forEach(function (method) { + const methodName = method.name; + const shouldPromisify = method.promises === true; - FileSystem.prototype[methodName] = function() { - var fs = this; - var args = Array.prototype.slice.call(arguments, 0); - var lastArgIndex = args.length - 1; + FileSystem.prototype[methodName] = function () { + const fs = this; + const args = Array.prototype.slice.call(arguments, 0); + const lastArgIndex = args.length - 1; // We may or may not get a callback, and since node.js supports // fire-and-forget style fs operations, we have to dance a bit here. - var missingCallback = typeof args[lastArgIndex] !== 'function'; - var callback = maybeCallback(args[lastArgIndex]); + const missingCallback = typeof args[lastArgIndex] !== 'function'; + const callback = maybeCallback(args[lastArgIndex]); // Deal with path arguments, validating and normalizing Buffer and file:// URLs - if(method.absPathArgs) { + if (method.absPathArgs) { method.absPathArgs.forEach(pathArg => processPathArg(args, pathArg, false)); } - if(method.relPathArgs) { + if (method.relPathArgs) { method.relPathArgs.forEach(pathArg => processPathArg(args, pathArg, true)); } - var error = fs.queueOrRun(function() { - var context = fs.provider.openReadWriteContext(); + const error = fs.queueOrRun(function () { + const context = fs.provider.openReadWriteContext(); // Fail early if the filesystem is in an error state (e.g., // provider failed to open. - if(FS_ERROR === fs.readyState) { - var err = new Errors.EFILESYSTEMERROR('filesystem unavailable, operation canceled'); + if (FS_ERROR === fs.readyState) { + const err = new Errors.EFILESYSTEMERROR('filesystem unavailable, operation canceled'); return callback.call(fs, err); } @@ -402,7 +407,7 @@ function FileSystem(options, callback) { } // Either add or replace the callback with our wrapper complete() - if(missingCallback) { + if (missingCallback) { args.push(complete); } else { args[lastArgIndex] = complete; @@ -411,16 +416,16 @@ function FileSystem(options, callback) { // Forward this call to the impl's version, using the following // call signature, with complete() as the callback/last-arg now: // fn(fs, context, arg0, arg1, ... , complete); - var fnArgs = [context].concat(args); + const fnArgs = [context].concat(args); impl[methodName].apply(null, fnArgs); }); - if(error) { + if (error) { callback(error); } }; - + // Add to fs.promises if appropriate - if(shouldPromisify) { + if (shouldPromisify) { FileSystem.prototype.promises[methodName] = promisify(FileSystem.prototype[methodName].bind(fs)); } });