update client-cache logic (#1626)

* client-cache: remove pulling

* client-cache: fix wrong ts field

* client-cache: purge cache on any local update

* client-cache: last_updated_time -> last_valid_time

* client-cache: update cache on remote update

* client-cache: update last_valid_ts every sec

* client-cache: switch to localstorage

* do the cache and purge test when `setAuthToken` is called

---------

Co-authored-by: Nariman Jelveh <nj@puter.com>
This commit is contained in:
Xiaochen Cui
2025-09-26 11:44:12 -07:00
committed by GitHub
co-authored by Nariman Jelveh
parent a0b567da52
commit 860388c3cb
7 changed files with 93 additions and 39 deletions
+87 -15
View File
@@ -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;
}
}
}
@@ -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();
})
}
@@ -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();
})
}
@@ -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();
})
}
@@ -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();
})
}
@@ -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();
})
}
@@ -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);