Files
2024-05-28 00:39:14 +10:00

219 lines
6.3 KiB
JavaScript

class ICache {
constructor() {}
async get(path) { throw new Error("NOT_IMPLEMENTED"); }
async store(path) { throw new Error("NOT_IMPLEMENTED"); }
async remove(path, exact = true) { throw new Error("NOT_IMPLEMENTED"); }
async update(path, fn) {
const data = await this.get(path);
return this.store(path, fn(data || {}));
}
async destroy() { throw new Error("NOT_IMPLEMENTED"); }
}
class InMemoryCache extends ICache {
constructor() {
super();
this.data = {};
}
async get(path) {
return this.data[this._key(path)] || null;
}
async store(path, obj) {
this.data[this._key(path)] = obj;
}
async remove(path, exact = true) {
if (!path) {
this.data = {};
return;
}
const key = this._key(path);
if (exact) {
delete this.data[key];
return;
}
for (const k in this.data) {
if (k.indexOf(key) === 0) {
delete data[k];
}
}
}
async destroy() {
this.data = {};
}
_key(path) {
return currentBackend() + "::" + currentShare() + "::" + path;
}
};
class IndexDBCache extends ICache {
DB_VERSION = 5;
FILE_PATH = "file_path";
db = null;
constructor() {
super();
const request = indexedDB.open("filestash", this.DB_VERSION);
request.onupgradeneeded = this._migration.bind(this);
this.db = new Promise((done, err) => {
request.onsuccess = (e) => {
done(e.target.result);
};
request.onerror = (e) => err("INDEXEDDB_NOT_SUPPORTED");
});
}
async get(path) {
const db = await this.db;
const tx = db.transaction(this.FILE_PATH, "readonly");
const store = tx.objectStore(this.FILE_PATH);
const query = store.get(this._key(path));
return await new Promise((done, error) => {
query.onsuccess = (e) => done(query.result || null)
query.onerror = () => done();
});
}
async store(path, value = {}) {
const db = await this.db;
const tx = db.transaction(this.FILE_PATH, "readwrite");
const store = tx.objectStore(this.FILE_PATH);
const request = store.put({
...value,
backend: currentBackend(),
share: currentShare(),
path: path,
});
return await new Promise((done, error) => {
done(value);
request.onsuccess = () => done(value);
request.onerror = err;
});
}
async remove(path, exact = true) {
const db = await this.db;
const tx = db.transaction(this.FILE_PATH, "readwrite");
const store = tx.objectStore(this.FILE_PATH);
const key = this._key(path);
if (exact !== true) {
let request = store.openCursor(IDBKeyRange.bound(
[key[0], key[1], key[2]],
[key[0], key[1], key[2]+"\u{FFFF}".repeat(5000)],
true, true,
));
await new Promise((done, err) => {
request.onsuccess = function(event) {
const cursor = event.target.result;
if (cursor) {
cursor.delete([key[0], key[1], cursor.value.path]);
cursor.continue();
return;
}
done();
};
request.onerror = err;
});
}
const req = store.delete(key);
return await new Promise((done, err) => {
req.onsuccess = () => done();
req.onerror = err;
});
}
_key(path) {
return [currentBackend(), currentShare(), path];
}
_migration(event) {
let store;
const db = event.target.result;
if (event.oldVersion === 1) {
// we've change the schema on v2 adding an index, let's flush
// to make sure everything will be fine
db.deleteObjectStore("file_path");
db.deleteObjectStore("file_content");
} else if (event.oldVersion === 2) {
// we've change the primary key to be a (path,share)
db.deleteObjectStore("file_path");
db.deleteObjectStore("file_content");
} else if (event.oldVersion === 3) {
// we've added a FILE_TAG to store tag related data and update
// keyPath to have "backend"
db.deleteObjectStore("file_path");
db.deleteObjectStore("file_content");
} else if (event.oldVersion === 4) {
// we got rid of the idea of offline first file manager so let's get rid of
// FILE_CONTENT and FILE_TAG
db.deleteObjectStore("file_path");
db.deleteObjectStore("file_content");
db.deleteObjectStore("file_tag");
}
store = db.createObjectStore(this.FILE_PATH, { keyPath: ["backend", "share", "path"] });
store.createIndex("idx_path", ["backend", "share", "path"], { unique: true });
}
}
let cache = null;
export function clearCache(path) {
console.log("TODO: clear cache");
// cache.clear(path);
}
export async function init() {
const setup_cache = () => {
cache = new InMemoryCache();
if (!("indexedDB" in window)) return initCacheState();
cache = new IndexDBCache();
return Promise.all([cache.db, initCacheState()]).catch((err) => {
if (err === "INDEXEDDB_NOT_SUPPORTED") {
// Firefox in private mode act like if it supports indexedDB but
// is throwing that string as an error if you try to use it ...
// so we fallback with our basic ram cache
cache = new DataFromMemory();
return initCacheState();
}
throw err;
});
}
const setup_session = () => {
return Promise.resolve();
};
return Promise.all([setup_cache(), setup_session()]);
}
export default function() {
return cache;
};
let backendID = "na";
export function currentBackend() {
return backendID;
}
export function currentShare() {
return new window.URL(location.href).searchParams.get("share") || "";
}
function initCacheState() {
return Promise.resolve();
}