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
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; |
|
}
|
|
|