diff --git a/src/puter-js/src/modules/FileSystem/index.js b/src/puter-js/src/modules/FileSystem/index.js index 70f8596f5..33541189c 100644 --- a/src/puter-js/src/modules/FileSystem/index.js +++ b/src/puter-js/src/modules/FileSystem/index.js @@ -2,7 +2,9 @@ import io from '../../lib/socket.io/socket.io.esm.min.js'; import * as utils from '../../lib/utils.js'; // Constants -const LAST_UPDATED_TS = 'last_updated_ts'; +// +// The last valid time of the local cache. +const LAST_VALID_TS = 'last_updated_ts'; // Operations import copy from './operations/copy.js'; @@ -71,6 +73,7 @@ export class PuterJSFileSystemModule extends AdvancedBase { this.APIOrigin = context.APIOrigin; this.appID = context.appID; this.context = context; + this.cacheUpdateTimer = null; // Connect socket. this.initializeSocket(); @@ -99,6 +102,9 @@ export class PuterJSFileSystemModule extends AdvancedBase { this.socket.disconnect(); } + // Stop any existing cache update timer + this.stopCacheUpdateTimer(); + this.socket = io(this.APIOrigin, { auth: { auth_token: this.authToken, @@ -111,11 +117,7 @@ export class PuterJSFileSystemModule extends AdvancedBase { bindSocketEvents() { this.socket.on('cache.updated', (item) => { - const local_ts = puter._cache.get(LAST_UPDATED_TS); - if (item.timestamp > local_ts || local_ts === undefined) { - console.log(`remote timestamp (${item.timestamp}) is newer than local timestamp (${local_ts}), flushing cache`); - puter._cache.flushall(); - } + this.postUpdate(); }); this.socket.on('connect', () => { @@ -130,10 +132,6 @@ export class PuterJSFileSystemModule extends AdvancedBase { { console.log('FileSystem Socket: Disconnected'); } - - // todo: NAIVE PURGE - // purge cache on disconnect since we may have become out of sync - puter._cache.flushall(); }); this.socket.on('reconnect', (attempt) => { @@ -181,6 +179,14 @@ export class PuterJSFileSystemModule extends AdvancedBase { */ setAuthToken(authToken) { this.authToken = authToken; + + // Check cache timestamp and purge if needed (only in GUI environment) + if (this.context.env === 'gui') { + this.checkCacheAndPurge(); + // Start background task to update LAST_VALID_TS every 1 second + this.startCacheUpdateTimer(); + } + // reset socket this.initializeSocket(); } @@ -199,15 +205,21 @@ export class PuterJSFileSystemModule extends AdvancedBase { } /** - * Updates the last updated timestamp in the cache. - * This should be called whenever file system operations modify the filesystem. + * The cache-related actions after local and remote updates. * * @memberof PuterJSFileSystemModule * @returns {void} */ - updateCacheTimestamp() { - // Add 1 second to mitigate clock skew and disable self-update. - puter._cache.set(LAST_UPDATED_TS, Date.now() + 1000); + postUpdate() { + // Action: Flush local cache + puter._cache.flushall(); + + // Action: Update last valid time + // + // Set to 0, which means the cache is not up to date. + localStorage.setItem(LAST_VALID_TS, '0'); + + console.log(`postUpdate triggered, LAST_VALID_TS: ${localStorage.getItem(LAST_VALID_TS)}`); } /** @@ -233,4 +245,64 @@ export class PuterJSFileSystemModule extends AdvancedBase { xhr.send(); }); } + + /** + * Checks cache timestamp and purges cache if needed. + * Only runs in GUI environment. + * + * @memberof PuterJSFileSystemModule + * @returns {void} + */ + async checkCacheAndPurge() { + try { + const serverTimestamp = await this.getCacheTimestamp(); + const localValidTs = parseInt(localStorage.getItem(LAST_VALID_TS)) || 0; + + console.log(`init comparison: serverTimestamp: ${serverTimestamp}, localValidTs: ${localValidTs}`); + if (serverTimestamp > localValidTs) { + console.log(`serverTimestamp > localValidTs, purging cache`); + // Server has newer data, purge local cache + puter._cache.flushall(); + localStorage.setItem(LAST_VALID_TS, '0'); + } + } catch (error) { + // If we can't get the server timestamp, silently fail + // This ensures the socket initialization doesn't break + console.error('Error checking cache timestamp:', error); + } + } + + /** + * Starts the background task to update LAST_VALID_TS every 1 second. + * Only runs in GUI environment. + * + * @memberof PuterJSFileSystemModule + * @returns {void} + */ + startCacheUpdateTimer() { + if (this.context.env !== 'gui') { + return; + } + + // Clear any existing timer + this.stopCacheUpdateTimer(); + + // Start new timer + this.cacheUpdateTimer = setInterval(() => { + localStorage.setItem(LAST_VALID_TS, Date.now().toString()); + }, 1000); + } + + /** + * Stops the background cache update timer. + * + * @memberof PuterJSFileSystemModule + * @returns {void} + */ + stopCacheUpdateTimer() { + if (this.cacheUpdateTimer) { + clearInterval(this.cacheUpdateTimer); + this.cacheUpdateTimer = null; + } + } } diff --git a/src/puter-js/src/modules/FileSystem/operations/copy.js b/src/puter-js/src/modules/FileSystem/operations/copy.js index 979fe7021..59fc1ae4a 100644 --- a/src/puter-js/src/modules/FileSystem/operations/copy.js +++ b/src/puter-js/src/modules/FileSystem/operations/copy.js @@ -56,10 +56,7 @@ const copy = function (...args) { dedupe_name: (options.dedupe_name || options.dedupeName), })); - this.updateCacheTimestamp(); - - // TOOD (xiaochen): puter desktop will have stale cache without this, find out why - puter._cache.flushall(); + this.postUpdate(); }) } diff --git a/src/puter-js/src/modules/FileSystem/operations/mkdir.js b/src/puter-js/src/modules/FileSystem/operations/mkdir.js index 0ebeab916..3858a29fe 100644 --- a/src/puter-js/src/modules/FileSystem/operations/mkdir.js +++ b/src/puter-js/src/modules/FileSystem/operations/mkdir.js @@ -54,10 +54,7 @@ const mkdir = function (...args) { create_missing_parents: (options.recursive || options.createMissingParents) ?? false, })); - this.updateCacheTimestamp(); - - // TOOD (xiaochen): puter desktop will have stale cache without this, find out why - puter._cache.flushall(); + this.postUpdate(); }) } diff --git a/src/puter-js/src/modules/FileSystem/operations/move.js b/src/puter-js/src/modules/FileSystem/operations/move.js index b8eff41f7..8bdadb8ae 100644 --- a/src/puter-js/src/modules/FileSystem/operations/move.js +++ b/src/puter-js/src/modules/FileSystem/operations/move.js @@ -66,10 +66,7 @@ const move = function (...args) { original_client_socket_id: options.excludeSocketID, })); - this.updateCacheTimestamp(); - - // TOOD (xiaochen): puter desktop will have stale cache without this, find out why - puter._cache.flushall(); + this.postUpdate(); }) } diff --git a/src/puter-js/src/modules/FileSystem/operations/rename.js b/src/puter-js/src/modules/FileSystem/operations/rename.js index 049d92cc8..c5156c6c2 100644 --- a/src/puter-js/src/modules/FileSystem/operations/rename.js +++ b/src/puter-js/src/modules/FileSystem/operations/rename.js @@ -51,10 +51,7 @@ const rename = function (...args) { xhr.send(JSON.stringify(dataToSend)); - this.updateCacheTimestamp(); - - // TOOD (xiaochen): puter desktop will have stale cache without this, find out why - puter._cache.flushall(); + this.postUpdate(); }) } diff --git a/src/puter-js/src/modules/FileSystem/operations/upload.js b/src/puter-js/src/modules/FileSystem/operations/upload.js index 533e74ee7..b23a74a96 100644 --- a/src/puter-js/src/modules/FileSystem/operations/upload.js +++ b/src/puter-js/src/modules/FileSystem/operations/upload.js @@ -432,10 +432,7 @@ const upload = async function(items, dirPath, options = {}){ // send request xhr.send(fd); - this.updateCacheTimestamp(); - - // TOOD (xiaochen): puter desktop will have stale cache without this, find out why - puter._cache.flushall(); + this.postUpdate(); }) } diff --git a/src/puter-js/src/modules/FileSystem/operations/write.js b/src/puter-js/src/modules/FileSystem/operations/write.js index 7aeca9980..b882ea571 100644 --- a/src/puter-js/src/modules/FileSystem/operations/write.js +++ b/src/puter-js/src/modules/FileSystem/operations/write.js @@ -55,10 +55,7 @@ const write = async function (targetPath, data, options = {}) { throw new Error({ code: 'field_invalid', message: 'write() data parameter is an invalid type' }); } - this.updateCacheTimestamp(); - - // TOOD (xiaochen): puter desktop will have stale cache without this, find out why - puter._cache.flushall(); + this.postUpdate(); // perform upload return this.upload(data, parent, options);