Module FileCache EnableExplicit ;- Constants ; Store names (also used in IDB::Init in General.sbi) #Store_Content = "file_content" #Store_Meta = "file_meta" ; Metadata JSON shape: ; { "path": "/home/alice/doc.txt", "dirty": false, "cached_at": 1234567890, "size": 42 } ;- Public procedures ; Store a file fetched from the server into the cache. ; Writes content and meta atomically in one transaction. Procedure Cache(FileID.s, Path.s, Content.s, *Callback) !var _cb = p_callback; !var _id = v_fileid; !var _path = v_path; !var _content = v_content; !var _db = window._kumos_idb; !if (!_db) { if (_cb) _cb(0, 'IDB not open'); return; } ! !var tx = _db.transaction(['file_content','file_meta'], 'readwrite'); !var contentStore = tx.objectStore('file_content'); !var metaStore = tx.objectStore('file_meta'); ! !var meta = JSON.stringify({ ! path: _path, ! dirty: false, ! cached_at: Date.now(), ! size: _content.length !}); ! !contentStore.put(_content, _id).onerror = function(e) { tx.abort(); }; !metaStore.put(meta, _id).onerror = function(e) { tx.abort(); }; ! !tx.oncomplete = function() { if (_cb) _cb(1, ''); }; !tx.onerror = function(e) { if (_cb) _cb(0, e.target.error ? e.target.error.message : 'cache failed'); }; !tx.onabort = function(e) { if (_cb) _cb(0, 'cache aborted'); }; EndProcedure ; Read cached content. Data = raw file content string. ; Data = "" if file is not in cache (Success is still #True). Procedure Read_(FileID.s, *Callback) IDB::Get(#Store_Content, FileID, *Callback) EndProcedure ; Write updated content to the cache and mark the file dirty. ; Patches meta without replacing path or other fields. Procedure Write(FileID.s, Content.s, *Callback) !var _cb = p_callback; !var _id = v_fileid; !var _content = v_content; !var _db = window._kumos_idb; !if (!_db) { if (_cb) _cb(0, 'IDB not open'); return; } ! !var tx = _db.transaction(['file_content','file_meta'], 'readwrite'); !var contentStore = tx.objectStore('file_content'); !var metaStore = tx.objectStore('file_meta'); ! !contentStore.put(_content, _id); ! !var getReq = metaStore.get(_id); !getReq.onsuccess = function(ev) { ! var meta = {}; ! try { meta = JSON.parse(ev.target.result || '{}'); } catch(e) {} ! meta.dirty = true; ! meta.size = _content.length; ! meta.cached_at = Date.now(); ! metaStore.put(JSON.stringify(meta), _id); !}; ! !tx.oncomplete = function() { if (_cb) _cb(1, ''); }; !tx.onerror = function(e) { if (_cb) _cb(0, e.target.error ? e.target.error.message : 'write failed'); }; EndProcedure ; Get the metadata JSON string for a cached file. Procedure GetMeta(FileID.s, *Callback) IDB::Get(#Store_Meta, FileID, *Callback) EndProcedure ; Mark a cached file as clean after a successful sync to the server. Procedure MarkClean(FileID.s, *Callback) !var _cb = p_callback; !var _id = v_fileid; !var _db = window._kumos_idb; !if (!_db) { if (_cb) _cb(0, 'IDB not open'); return; } ! !var tx = _db.transaction(['file_meta'], 'readwrite'); !var store = tx.objectStore('file_meta'); !var req = store.get(_id); !req.onsuccess = function(ev) { ! var meta = {}; ! try { meta = JSON.parse(ev.target.result || '{}'); } catch(e) {} ! meta.dirty = false; ! store.put(JSON.stringify(meta), _id); !}; ! !tx.oncomplete = function() { if (_cb) _cb(1, ''); }; !tx.onerror = function(e) { if (_cb) _cb(0, e.target.error ? e.target.error.message : 'markclean failed'); }; EndProcedure ; Remove a file from the cache entirely (both content and meta). Procedure Evict(FileID.s, *Callback) !var _cb = p_callback; !var _id = v_fileid; !var _db = window._kumos_idb; !if (!_db) { if (_cb) _cb(0, 'IDB not open'); return; } ! !var tx = _db.transaction(['file_content','file_meta'], 'readwrite'); !tx.objectStore('file_content').delete(_id); !tx.objectStore('file_meta').delete(_id); ! !tx.oncomplete = function() { if (_cb) _cb(1, ''); }; !tx.onerror = function(e) { if (_cb) _cb(0, e.target.error ? e.target.error.message : 'evict failed'); }; EndProcedure ; Return all cached file IDs as a JSON array. Procedure ListCached(*Callback) IDB::GetAllKeys(#Store_Meta, *Callback) EndProcedure ; Return only the dirty file IDs as a JSON array. ; These are files that have been written locally but not yet synced. Procedure ListDirty(*Callback) !var _cb = p_callback; !var _db = window._kumos_idb; !if (!_db) { if (_cb) _cb(0, 'IDB not open'); return; } ! !var tx = _db.transaction(['file_meta'], 'readonly'); !var store = tx.objectStore('file_meta'); !var req = store.openCursor(); !var dirty = []; ! !req.onsuccess = function(ev) { ! var cursor = ev.target.result; ! if (cursor) { ! try { ! var meta = JSON.parse(cursor.value); ! if (meta.dirty) dirty.push(String(cursor.key)); ! } catch(e) {} ! cursor.continue(); ! } !}; ! !tx.oncomplete = function() { if (_cb) _cb(1, JSON.stringify(dirty)); }; !tx.onerror = function(e) { if (_cb) _cb(0, e.target.error ? e.target.error.message : 'listdirty failed'); }; EndProcedure ; Returns "1" if the file is in the cache, "0" if not. Procedure Exists(FileID.s, *Callback) IDB::Exists(#Store_Meta, FileID, *Callback) EndProcedure EndModule ; IDE Options = SpiderBasic 3.20 (Windows - x86) ; CursorPosition = 156 ; Folding = Bw ; iOSAppOrientation = 0 ; AndroidAppCode = 0 ; AndroidAppOrientation = 0 ; EnableXP ; DPIAware ; CompileSourceDirectory