THU Hole react frontend
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

174 lines
5.1 KiB

const HOLE_CACHE_DB_NAME = 'hole_cache_db';
const CACHE_DB_VER = 1;
const MAINTENANCE_STEP = 150;
const MAINTENANCE_COUNT = 1000;
const ENC_KEY = 42;
class Cache {
constructor() {
this.db = null;
this.added_items_since_maintenance = 0;
this.encrypt = this.encrypt.bind(this);
this.decrypt = this.decrypt.bind(this);
const open_req = indexedDB.open(HOLE_CACHE_DB_NAME, CACHE_DB_VER);
open_req.onerror = console.error.bind(console);
open_req.onupgradeneeded = (event) => {
console.log('comment cache db upgrade');
const db = event.target.result;
const store = db.createObjectStore('comment', {
keyPath: 'pid',
});
store.createIndex('last_access', 'last_access', { unique: false });
};
open_req.onsuccess = (event) => {
console.log('comment cache db loaded');
this.db = event.target.result;
setTimeout(this.maintenance.bind(this), 1);
};
}
// use window.hole_cache.encrypt() only after cache is loaded!
encrypt(pid, data) {
let s = JSON.stringify(data);
let o = '';
for (let i = 0, key = (ENC_KEY ^ pid) % 128; i < s.length; i++) {
let c = s.charCodeAt(i);
let new_key = (key ^ (c / 2)) % 128;
o += String.fromCharCode(key ^ s.charCodeAt(i));
key = new_key;
}
return o;
}
// use window.hole_cache.decrypt() only after cache is loaded!
decrypt(pid, s) {
let o = '';
if (typeof s !== typeof 'str') return null;
for (let i = 0, key = (ENC_KEY ^ pid) % 128; i < s.length; i++) {
let c = key ^ s.charCodeAt(i);
o += String.fromCharCode(c);
key = (key ^ (c / 2)) % 128;
}
try {
return JSON.parse(o);
} catch (e) {
console.error('decrypt failed');
console.trace(e);
return null;
}
}
get(pid, target_version) {
pid = parseInt(pid);
return new Promise((resolve, reject) => {
if (!this.db) return resolve(null);
const tx = this.db.transaction(['comment'], 'readwrite');
const store = tx.objectStore('comment');
const get_req = store.get(pid);
get_req.onsuccess = () => {
let res = get_req.result;
if (!res || !res.data_str) {
//console.log('comment cache miss '+pid);
resolve(null);
} else if (target_version === res.version) {
// hit
//console.log('comment cache hit', pid);
res.last_access = +new Date();
store.put(res);
let data = this.decrypt(pid, res.data_str);
resolve(data); // obj or null
} else {
// expired
console.log(
'comment cache expired',
pid,
': ver',
res.version,
'target',
target_version,
);
store.delete(pid);
resolve(null);
}
};
get_req.onerror = (e) => {
console.warn('comment cache indexeddb open failed');
console.error(e);
resolve(null);
};
});
}
put(pid, target_version, data) {
pid = parseInt(pid);
return new Promise((resolve, reject) => {
if (!this.db) return resolve();
const tx = this.db.transaction(['comment'], 'readwrite');
const store = tx.objectStore('comment');
store.put({
pid: pid,
version: target_version,
data_str: this.encrypt(pid, data),
last_access: +new Date(),
});
//console.log('comment cache put', pid);
if (++this.added_items_since_maintenance === MAINTENANCE_STEP)
setTimeout(this.maintenance.bind(this), 1);
});
}
delete(pid) {
pid = parseInt(pid);
return new Promise((resolve, reject) => {
if (!this.db) return resolve();
const tx = this.db.transaction(['comment'], 'readwrite');
const store = tx.objectStore('comment');
let req = store.delete(pid);
console.log('comment cache delete', pid);
req.onerror = () => {
console.warn('comment cache delete failed ', pid);
return resolve();
};
req.onsuccess = () => resolve();
});
}
maintenance() {
if (!this.db) return;
const tx = this.db.transaction(['comment'], 'readwrite');
const store = tx.objectStore('comment');
let count_req = store.count();
count_req.onsuccess = () => {
let count = count_req.result;
if (count > MAINTENANCE_COUNT) {
console.log('comment cache db maintenance', count);
store.index('last_access').openKeyCursor().onsuccess = (e) => {
let cur = e.target.result;
if (cur) {
//console.log('maintenance: delete',cur);
store.delete(cur.primaryKey);
if (--count > MAINTENANCE_COUNT) cur.continue();
}
};
} else {
console.log('comment cache db no need to maintenance', count);
}
this.added_items_since_maintenance = 0;
};
count_req.onerror = console.error.bind(console);
}
clear() {
if (!this.db) return;
indexedDB.deleteDatabase(HOLE_CACHE_DB_NAME);
console.log('delete comment cache db');
}
}
export function cache() {
if (!window.hole_cache) window.hole_cache = new Cache();
return window.hole_cache;
}