diff --git a/__tests__/adapters/vfs/system.js b/__tests__/adapters/vfs/system.js index b8a955c..08e0fb0 100644 --- a/__tests__/adapters/vfs/system.js +++ b/__tests__/adapters/vfs/system.js @@ -15,16 +15,6 @@ describe('VFS System adapter', () => { afterAll(() => core.destroy()); const vfs = { - req: { - session: { - user: { - username: 'jest' - } - } - }, - res: { - - }, mount: { name: 'home', root: 'home:/', @@ -34,10 +24,19 @@ describe('VFS System adapter', () => { } }; + const createOptions = (options = {}) => ({ + ...options, + session: { + user: { + username: 'jest' + } + } + }); + const request = (name, ...args) => adapter[name](vfs, vfs)(...args); test('#touch', () => { - return expect(request('touch', 'home:/test')) + return expect(request('touch', 'home:/test', createOptions())) .resolves .toBe(true); }); @@ -45,7 +44,7 @@ describe('VFS System adapter', () => { test('#stat', () => { const realPath = path.join(core.configuration.tempPath, 'jest/test'); - return expect(request('stat', 'home:/test')) + return expect(request('stat', 'home:/test', createOptions())) .resolves .toMatchObject({ filename: 'test', @@ -58,37 +57,37 @@ describe('VFS System adapter', () => { }); test('#copy', () => { - return expect(request('copy', 'home:/test', 'home:/test-copy')) + return expect(request('copy', 'home:/test', 'home:/test-copy', createOptions())) .resolves .toBe(true); }); test('#rename', () => { - return expect(request('rename', 'home:/test-copy', 'home:/test-rename')) + return expect(request('rename', 'home:/test-copy', 'home:/test-rename', createOptions())) .resolves .toBe(true); }); test('#mkdir', () => { - return expect(request('mkdir', 'home:/test-directory')) + return expect(request('mkdir', 'home:/test-directory', createOptions())) .resolves .toBe(true); }); test('#mkdir - existing directory', () => { - return expect(request('mkdir', 'home:/test-directory')) + return expect(request('mkdir', 'home:/test-directory', createOptions())) .rejects .toThrowError(); }); test('#mkdir - ensure', () => { - return expect(request('mkdir', 'home:/test-directory', {ensure: true})) + return expect(request('mkdir', 'home:/test-directory', createOptions({ensure: true}))) .resolves .toBe(true); }); test('#readfile', () => { - return expect(request('readfile', 'home:/test')) + return expect(request('readfile', 'home:/test', createOptions())) .resolves .toBeInstanceOf(stream.Readable); }); @@ -99,31 +98,31 @@ describe('VFS System adapter', () => { s.push('jest'); s.push(null); - return expect(request('writefile', 'home:/test', s)) + return expect(request('writefile', 'home:/test', s, createOptions())) .resolves .toBe(true); }); test('#exists - existing file', () => { - return expect(request('exists', 'home:/test-rename')) + return expect(request('exists', 'home:/test-rename', createOptions())) .resolves .toBe(true); }); test('#exists - existing directory', () => { - return expect(request('exists', 'home:/test-directory')) + return expect(request('exists', 'home:/test-directory', createOptions())) .resolves .toBe(true); }); test('#exists - non existing file', () => { - return expect(request('exists', 'home:/test-copy')) + return expect(request('exists', 'home:/test-copy', createOptions())) .resolves .toBe(false); }); test('#search', () => { - return expect(request('search', 'home:/', '*')) + return expect(request('search', 'home:/', '*', createOptions())) .resolves .toEqual( expect.arrayContaining([ @@ -140,7 +139,7 @@ describe('VFS System adapter', () => { }); test('#readdir', () => { - return expect(request('readdir', 'home:/')) + return expect(request('readdir', 'home:/', createOptions())) .resolves .toEqual( expect.arrayContaining([ @@ -164,14 +163,14 @@ describe('VFS System adapter', () => { const files = ['home:/test', 'home:/test-directory', 'home:/test-rename']; return Promise.all(files.map(f => { - return expect(request('unlink', f)) + return expect(request('unlink', f, createOptions())) .resolves .toBe(true); })); }); test('#unlink', () => { - return expect(request('unlink', 'home:/test-directory')) + return expect(request('unlink', 'home:/test-directory', createOptions())) .resolves .toBe(true); }); @@ -179,7 +178,7 @@ describe('VFS System adapter', () => { test('#realpath', () => { const realPath = path.join(core.configuration.tempPath, 'jest/test'); - return expect(request('realpath', 'home:/test')) + return expect(request('realpath', 'home:/test', createOptions())) .resolves .toBe(realPath); }); diff --git a/__tests__/filesystem.js b/__tests__/filesystem.js index 733f379..8241fa8 100644 --- a/__tests__/filesystem.js +++ b/__tests__/filesystem.js @@ -46,8 +46,8 @@ describe('Filesystem', () => { .toBe('test/jest'); }); - test('#mount', () => { - mountpoint = filesystem.mount({ + test('#mount', async () => { + mountpoint = await filesystem.mount({ name: 'jest', attributes: { root: '/tmp' diff --git a/src/adapters/vfs/system.js b/src/adapters/vfs/system.js index 2cd25a2..1469c7d 100644 --- a/src/adapters/vfs/system.js +++ b/src/adapters/vfs/system.js @@ -80,14 +80,14 @@ const segments = { username: { dynamic: true, - fn: (core, req) => req.session.user.username + fn: (core, session) => session.user.username } }; /* * Gets a segment value */ -const getSegment = (core, req, seg) => segments[seg] ? segments[seg].fn(core, req) : ''; +const getSegment = (core, session, seg) => segments[seg] ? segments[seg].fn(core, session) : ''; /* * Matches a string for segments @@ -97,16 +97,16 @@ const matchSegments = str => (str.match(/(\{\w+\})/g) || []); /* * Resolves a string with segments */ -const resolveSegments = (core, req, str) => matchSegments(str) - .reduce((result, current) => result.replace(current, getSegment(core, req, current.replace(/(\{|\})/g, ''))), str); +const resolveSegments = (core, session, str) => matchSegments(str) + .reduce((result, current) => result.replace(current, getSegment(core, session, current.replace(/(\{|\})/g, ''))), str); /* * Resolves a given file path based on a request * Will take out segments from the resulting string * and replace them with a list of defined variables */ -const getRealPath = (core, req, mount, file) => { - const root = resolveSegments(core, req, mount.attributes.root); +const getRealPath = (core, session, mount, file) => { + const root = resolveSegments(core, session, mount.attributes.root); const str = file.substr(mount.root.length - 1); return path.join(root, str); }; @@ -118,7 +118,7 @@ const getRealPath = (core, req, mount, file) => { */ module.exports = (core) => { const wrapper = (method, cb, ...args) => vfs => (file, options = {}) => { - const promise = Promise.resolve(getRealPath(core, vfs.req, vfs.mount, file)) + const promise = Promise.resolve(getRealPath(core, options.session, vfs.mount, file)) .then(realPath => fs[method](realPath, ...args)); return typeof cb === 'function' @@ -127,8 +127,8 @@ module.exports = (core) => { }; const crossWrapper = method => (srcVfs, destVfs) => (src, dest, options = {}) => Promise.resolve({ - realSource: getRealPath(core, srcVfs.req, srcVfs.mount, src), - realDest: getRealPath(core, destVfs.req, destVfs.mount, dest) + realSource: getRealPath(core, options.session, srcVfs.mount, src), + realDest: getRealPath(core, options.session, destVfs.mount, dest) }) .then(({realSource, realDest}) => fs[method](realSource, realDest)) .then(() => true); @@ -136,10 +136,8 @@ module.exports = (core) => { return { watch: (mount, callback) => { const dest = resolveSegments(core, { - session: { - user: { - username: '**' - } + user: { + username: '**' } }, mount.attributes.root); @@ -168,6 +166,7 @@ module.exports = (core) => { /** * Checks if file exists * @param {String} file The file path from client + * @param {Object} [options={}] Options * @return {Promise} */ exists: wrapper('access', promise => { @@ -178,10 +177,11 @@ module.exports = (core) => { /** * Get file statistics * @param {String} file The file path from client + * @param {Object} [options={}] Options * @return {Object} */ - stat: vfs => file => - Promise.resolve(getRealPath(core, vfs.req, vfs.mount, file)) + stat: vfs => (file, options = {}) => + Promise.resolve(getRealPath(core, options.session, vfs.mount, file)) .then(realPath => { return fs.access(realPath, fs.F_OK) .then(() => createFileIter(core, path.dirname(realPath), realPath)); @@ -190,10 +190,11 @@ module.exports = (core) => { /** * Reads directory * @param {String} root The file path from client + * @param {Object} [options={}] Options * @return {Object[]} */ - readdir: vfs => root => - Promise.resolve(getRealPath(core, vfs.req, vfs.mount, root)) + readdir: vfs => (root, options) => + Promise.resolve(getRealPath(core, options.session, vfs.mount, root)) .then(realPath => fs.readdir(realPath).then(files => ({realPath, files}))) .then(({realPath, files}) => { const promises = files.map(f => createFileIter(core, realPath, root.replace(/\/?$/, '/') + f)); @@ -203,10 +204,11 @@ module.exports = (core) => { /** * Reads file stream * @param {String} file The file path from client + * @param {Object} [options={}] Options * @return {stream.Readable} */ readfile: vfs => (file, options = {}) => - Promise.resolve(getRealPath(core, vfs.req, vfs.mount, file)) + Promise.resolve(getRealPath(core, options.session, vfs.mount, file)) .then(realPath => fs.stat(realPath).then(stat => ({realPath, stat}))) .then(({realPath, stat}) => { if (!stat.isFile()) { @@ -224,9 +226,10 @@ module.exports = (core) => { /** * Creates directory * @param {String} file The file path from client + * @param {Object} [options={}] Options * @return {boolean} */ - mkdir: wrapper('mkdir', (promise, options) => { + mkdir: wrapper('mkdir', (promise, options = {}) => { return promise .then(() => true) .catch(e => { @@ -242,13 +245,14 @@ module.exports = (core) => { * Writes file stream * @param {String} file The file path from client * @param {stream.Readable} data The stream + * @param {Object} [options={}] Options * @return {Promise} */ - writefile: vfs => (file, data) => new Promise((resolve, reject) => { + writefile: vfs => (file, data, options = {}) => new Promise((resolve, reject) => { // FIXME: Currently this actually copies the file because // formidable will put this in a temporary directory. // It would probably be better to do a "rename()" on local filesystems - const realPath = getRealPath(core, vfs.req, vfs.mount, file); + const realPath = getRealPath(core, options.session, vfs.mount, file); const write = () => { const stream = fs.createWriteStream(realPath); @@ -270,6 +274,7 @@ module.exports = (core) => { * Renames given file or directory * @param {String} src The source file path from client * @param {String} dest The destination file path from client + * @param {Object} [options={}] Options * @return {boolean} */ rename: crossWrapper('rename'), @@ -278,6 +283,7 @@ module.exports = (core) => { * Copies given file or directory * @param {String} src The source file path from client * @param {String} dest The destination file path from client + * @param {Object} [options={}] Options * @return {boolean} */ copy: crossWrapper('copy'), @@ -285,6 +291,7 @@ module.exports = (core) => { /** * Removes given file or directory * @param {String} file The file path from client + * @param {Object} [options={}] Options * @return {boolean} */ unlink: wrapper('remove'), @@ -292,10 +299,11 @@ module.exports = (core) => { /** * Searches for files and folders * @param {String} file The file path from client + * @param {Object} [options={}] Options * @return {boolean} */ - search: vfs => (root, pattern) => - Promise.resolve(getRealPath(core, vfs.req, vfs.mount, root)) + search: vfs => (root, pattern, options = {}) => + Promise.resolve(getRealPath(core, options.session, vfs.mount, root)) .then(realPath => { return fh.create() .paths(realPath) @@ -323,6 +331,7 @@ module.exports = (core) => { /** * Touches a file * @param {String} file The file path from client + * @param {Object} [options={}] Options * @return {boolean} */ touch: wrapper('ensureFile'), @@ -330,9 +339,10 @@ module.exports = (core) => { /** * Gets the real filesystem path (internal only) * @param {String} file The file path from client + * @param {Object} [options={}] Options * @return {string} */ - realpath: vfs => file => - Promise.resolve(getRealPath(core, vfs.req, vfs.mount, file)) + realpath: vfs => (file, options = {}) => + Promise.resolve(getRealPath(core, options.session, vfs.mount, file)) }; }; diff --git a/src/filesystem.js b/src/filesystem.js index 31fb981..e04dbab 100644 --- a/src/filesystem.js +++ b/src/filesystem.js @@ -101,8 +101,8 @@ class Filesystem { mime.define(define, {force: true}); // Mountpoints - this.core.config('vfs.mountpoints') - .forEach(mount => this.mount(mount)); + await Promise.all(this.core.config('vfs.mountpoints') + .map(mount => this.mount(mount))); return true; } @@ -192,7 +192,7 @@ class Filesystem { * @param {object} mount Mountpoint * @return {object} the mountpoint */ - mount(mount) { + async mount(mount) { const mountpoint = { id: uuid(), root: `${mount.name}:/`, @@ -204,7 +204,7 @@ class Filesystem { logger.success('Mounted', mountpoint.name); - this.watch(mountpoint); + await this.watch(mountpoint); return mountpoint; } @@ -236,7 +236,7 @@ class Filesystem { * Set up a watch for given mountpoint * @param {object} mountpoint The mountpoint */ - watch(mountpoint) { + async watch(mountpoint) { if ( !mountpoint.attributes.watch || this.core.config('vfs.watch') === false || @@ -245,12 +245,12 @@ class Filesystem { return; } - const adapter = mountpoint.adapter + const adapter = await (mountpoint.adapter ? this.adapters[mountpoint.adapter] - : this.adapters.system; + : this.adapters.system); if (typeof adapter.watch === 'function') { - this._watch(mountpoint, adapter); + await this._watch(mountpoint, adapter); } } @@ -259,8 +259,8 @@ class Filesystem { * @param {object} mountpoint The mountpoint * @param {object} adapter The adapter */ - _watch(mountpoint, adapter) { - const watch = adapter.watch(mountpoint, (args, dir, type) => { + async _watch(mountpoint, adapter) { + const watch = await adapter.watch(mountpoint, (args, dir, type) => { const target = mountpoint.name + ':/' + dir; const keys = Object.keys(args); const filter = keys.length === 0 diff --git a/src/vfs.js b/src/vfs.js index 6bfd1f9..d7d38d9 100644 --- a/src/vfs.js +++ b/src/vfs.js @@ -113,6 +113,7 @@ const createMiddleware = core => { const createOptions = req => { const options = req.fields.options; const range = req.headers && req.headers.range; + const session = {...req.session || {}}; let result = options || {}; if (typeof options === 'string') { @@ -127,7 +128,10 @@ const createOptions = req => { result.range = parseRangeHeader(req.headers.range); } - return result; + return { + ...result, + session + }; }; // Standard request with only a target @@ -145,7 +149,7 @@ const createRequestFactory = findMountpoint => (getter, method, readOnly, respon const {attributes} = found.mount; const strict = attributes.strictGroups !== false; const ranges = (!attributes.adapter || attributes.adapter === 'system') || attributes.ranges === true; - const wrapper = m => found.adapter[m]({req, res, adapter: found.adapter, mount: found.mount})(...args); + const wrapper = m => found.adapter[m]({adapter: found.adapter, mount: found.mount})(...args); const readstat = () => wrapper('stat').catch(() => ({})); await checkMountpointPermission(req, res, method, readOnly, strict)(found); @@ -191,7 +195,7 @@ const createCrossRequestFactory = findMountpoint => (getter, method, respond) => const srcMount = await findMountpoint(from); const destMount = await findMountpoint(to); const sameAdapter = srcMount.adapter === destMount.adapter; - const createArgs = t => ({req, res, adapter: t.adapter, mount: t.mount}); + const createArgs = t => ({adapter: t.adapter, mount: t.mount}); const srcStrict = srcMount.mount.attributes.strictGroups !== false; const destStrict = destMount.mount.attributes.strictGroups !== false;