diff --git a/extensions/puterfs/PuterFSProvider.js b/extensions/puterfs/PuterFSProvider.js index 156a92cdc..2e2526b13 100644 --- a/extensions/puterfs/PuterFSProvider.js +++ b/extensions/puterfs/PuterFSProvider.js @@ -73,6 +73,7 @@ const { NodeChildSelector, NodeUIDSelector, NodeInternalIDSelector, + NodeRawEntrySelector, } = extension.import('core').fs.selectors; const { @@ -112,6 +113,7 @@ export default class PuterFSProvider { capabilities.UUID, capabilities.OPERATION_TRACE, capabilities.READDIR_UUID_MODE, + capabilities.READDIRSTAT_UUID, capabilities.PUTER_SHORTCUT, capabilities.COPY_TREE, @@ -206,6 +208,29 @@ export default class PuterFSProvider { } // #endregion + // #region Optimization + /** + * The readdirstat_uuid operation is only available for filesystem + * immplementations with READDIR_UUID_MODE enabled. This implements + * an optimized readdir operation when the UUID is already known. + * @param {*} param0 + */ + async readdirstat_uuid ({ + uuid, + options = {}, + }) { + const entries = await this.fsEntryController.get_descendants_full(uuid, options); + const nodes = Promise.all(Array.prototype.map.call(entries, raw_entry => { + const node = svc_fs.node(new NodeRawEntrySelector(raw_entry, { + found_thumbnail: options.thumbnail, + })); + node.found = true; // TODO: how is it possible for this to be false? + return node; + })); + return nodes; + }; + // #endregion + // #region Standard FS /** @@ -374,6 +399,11 @@ export default class PuterFSProvider { let entry; + // stat doesn't work with RawEntrySelector + if ( selector instanceof NodeRawEntrySelector ) { + selector = new NodeUIDSelector(node.uid); + } + await new Promise (rslv => { const detachables = new MultiDetachable(); diff --git a/extensions/puterfs/fsentries/FSEntryController.js b/extensions/puterfs/fsentries/FSEntryController.js index b87234870..4110db1be 100644 --- a/extensions/puterfs/fsentries/FSEntryController.js +++ b/extensions/puterfs/fsentries/FSEntryController.js @@ -208,6 +208,12 @@ export default class { return answer.entry; } + /** + * Returns UUIDs of child fsentries under the specified + * parent fsentry + * @param {string} uuid - UUID of parent fsentry + * @returns fsentry[] + */ async get_descendants (uuid) { return uuid === PuterPath.NULL_UUID ? await db.read('SELECT uuid FROM fsentries WHERE parent_uid IS NULL', @@ -217,6 +223,25 @@ export default class { ; } + /** + * Returns full fsentry nodes for entries under the specified + * parent fsentry + * @param {string} uuid - UUID of parent fsentry + * @returns fsentry[] + */ + async get_descendants_full (uuid, fetch_entry_options) { + const { thumbnail } = fetch_entry_options; + const columns = `${ + this.defaultProperties.join(', ') + }${thumbnail ? ', thumbnail' : ''}`; + return uuid === PuterPath.NULL_UUID + ? await db.read(`SELECT ${columns} FROM fsentries WHERE parent_uid IS NULL`, + [uuid]) + : await db.read(`SELECT ${columns} FROM fsentries WHERE parent_uid = ?`, + [uuid]) + ; + } + async get_recursive_size (uuid) { const cte_query = ` WITH RECURSIVE descendant_cte AS ( diff --git a/src/backend/src/filesystem/FSNodeContext.js b/src/backend/src/filesystem/FSNodeContext.js index ad80f4778..c4f00748c 100644 --- a/src/backend/src/filesystem/FSNodeContext.js +++ b/src/backend/src/filesystem/FSNodeContext.js @@ -84,7 +84,7 @@ module.exports = class FSNodeContext { }) { const ecmap = Context.get(ECMAP.SYMBOL); - if ( ecmap ) { + if ( ecmap && !(selector instanceof NodeRawEntrySelector) ) { // We might return an existing FSNodeContext const maybe_node = ecmap ?.get_fsNodeContext_from_selector?.(selector); diff --git a/src/backend/src/filesystem/definitions/capabilities.js b/src/backend/src/filesystem/definitions/capabilities.js index 241184c61..6ed45f30e 100644 --- a/src/backend/src/filesystem/definitions/capabilities.js +++ b/src/backend/src/filesystem/definitions/capabilities.js @@ -37,6 +37,7 @@ const capabilityNames = [ 'move-tree', 'remove-tree', 'get-recursive-size', + 'readdirstat_uuid', // Behavior Capabilities 'case-sensitive', diff --git a/src/backend/src/filesystem/ll_operations/ll_readdir.js b/src/backend/src/filesystem/ll_operations/ll_readdir.js index 428a23067..ecae12fc8 100644 --- a/src/backend/src/filesystem/ll_operations/ll_readdir.js +++ b/src/backend/src/filesystem/ll_operations/ll_readdir.js @@ -73,6 +73,27 @@ class LLReadDir extends LLFilesystemOperation { const capabilities = subject.provider.get_capabilities(); // UUID Mode + optimization: { + const uuid_selector = subject.get_selector_of_type(NodeUIDSelector); + + // Skip this optimization if there is no UUID + if ( ! uuid_selector ) { + break optimization; + } + + // Skip this optimization if the filesystem doesn't implement + // the "readdirstat_uuid" macro operation. + if ( ! capabilities.has(fsCapabilities.READDIRSTAT_UUID) ) { + break optimization; + } + + const uuid = uuid_selector.value; + return await subject.provider.readdirstat_uuid({ + uuid, + options: { thumbnail: true }, + }); + } + if ( capabilities.has(fsCapabilities.READDIR_UUID_MODE) ) { this.checkpoint('readdir uuid mode'); const child_uuids = await subject.provider.readdir({ diff --git a/src/backend/src/filesystem/node/selectors.js b/src/backend/src/filesystem/node/selectors.js index 79c2eab98..3572ac3ba 100644 --- a/src/backend/src/filesystem/node/selectors.js +++ b/src/backend/src/filesystem/node/selectors.js @@ -141,8 +141,13 @@ class RootNodeSelector extends NodeSelector { } class NodeRawEntrySelector extends NodeSelector { - constructor (entry) { + constructor (entry, details_about_fetch = {}) { super(); + + // The `details_about_fetch` object lets us simulate non-entry state + // that occurs after a node has been fetched + this.details_about_fetch = details_about_fetch; + // Fix entries from get_descendants if ( !entry.uuid && entry.uid ) { entry.uuid = entry.uid; @@ -156,6 +161,9 @@ class NodeRawEntrySelector extends NodeSelector { } setPropertiesKnownBySelector (node) { + if ( this.details_about_fetch.found_thumbnail ) { + node.found_thumbnail = true; + } node.found = true; node.entry = this.entry; node.uid = this.entry.uid ?? this.entry.uuid;