From e97c939831b490c3b5216f61bbf05f5dea6eeada Mon Sep 17 00:00:00 2001 From: xmcp Date: Wed, 19 Jun 2019 13:07:41 +0800 Subject: [PATCH] add comment cache --- src/Flows.js | 20 ++++++---- src/cache.js | 98 ++++++++++++++++++++++++++++++++++++++++++++++++ src/flows_api.js | 30 ++++++++++++++- 3 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 src/cache.js diff --git a/src/Flows.js b/src/Flows.js index 18ed4938..14243c84 100644 --- a/src/Flows.js +++ b/src/Flows.js @@ -30,13 +30,17 @@ function load_single_meta(show_sidebar,token,parents) { 正在加载 #{pid} ); - Promise.all([ - API.get_single(pid,token), - API.load_replies(pid,token,color_picker), - ]) - .then((res)=>{ - const [single,replies]=res; + API.get_single(pid,token) + .then((single)=>{ single.data.variant={}; + return new Promise((resolve,reject)=>{ + API.load_replies_with_cache(pid,token,color_picker,parseInt(single.data.reply)) + .then((replies)=>{resolve([single,replies])}) + .catch(reject); + }); + }) + .then((res)=>{ + let [single,replies]=res; show_sidebar( title_elem, { this.setState((prev,props)=>({ replies: json.data, @@ -401,7 +405,7 @@ class FlowItemRow extends PureComponent { this.setState({ reply_status: 'loading', }); - API.load_replies(this.state.info.pid,this.props.token,this.color_picker) + API.load_replies_with_cache(this.state.info.pid,this.props.token,this.color_picker,parseInt(this.state.info.reply)) .then((json)=>{ this.setState((prev,props)=>({ replies: json.data, diff --git a/src/cache.js b/src/cache.js new file mode 100644 index 00000000..99df70ff --- /dev/null +++ b/src/cache.js @@ -0,0 +1,98 @@ +const CACHE_DB_VER=1; +const MAINTENANCE_STEP=500; +const MAINTENANCE_COUNT=5000; + +class Cache { + constructor() { + this.db=null; + this.added_items_since_maintenance=0; + const open_req=indexedDB.open('hole_cache_db',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); + }; + } + + get(pid,target_version) { + 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) { + console.log('cache miss'); + resolve(null); + } else if(target_version===res.version) { // hit + console.log('cache hit'); + res.last_access=+new Date(); + store.put(res); + resolve(res.data); + } else { // expired + console.log('cache expired: ver',res.version,'target',target_version); + store.delete(pid); + resolve(null); + } + }; + get_req.onerror=reject; + }); + } + + put(pid,target_version,data) { + 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: data, + last_access: +new Date(), + }); + if(++this.added_items_since_maintenance===MAINTENANCE_STEP) + setTimeout(this.maintenance.bind(this),1); + }); + } + + 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 not full',count); + } + this.added_items_since_maintenance=0; + }; + count_req.onerror=console.error.bind(console); + } +}; + +export let cache=new Cache(); \ No newline at end of file diff --git a/src/flows_api.js b/src/flows_api.js index b79d5052..e3238cd0 100644 --- a/src/flows_api.js +++ b/src/flows_api.js @@ -1,6 +1,7 @@ import {get_json} from './infrastructure/functions'; import {PKUHELPER_ROOT} from './infrastructure/const'; import {API_BASE} from './Common'; +import {cache} from './cache'; export const API_VERSION_PARAM='&PKUHelperAPI=3.0'; export {PKUHELPER_ROOT}; @@ -12,7 +13,8 @@ function token_param(token) { export {get_json}; export const API={ - load_replies: (pid,token,color_picker)=>{ + load_replies: (pid,token,color_picker,cache_version)=>{ + pid=parseInt(pid); return fetch( API_BASE+'/api.php?action=getcomment'+ '&pid='+pid+ @@ -25,6 +27,9 @@ export const API={ else throw new Error(JSON.stringify(json)); } + cache.put(pid,cache_version,json); + + // also change load_replies_with_cache! json.data=json.data .sort((a,b)=>{ return parseInt(a.timestamp,10)-parseInt(b.timestamp,10); @@ -39,6 +44,29 @@ export const API={ }); }, + load_replies_with_cache: (pid,token,color_picker,cache_version)=> { + pid=parseInt(pid); + return cache.get(pid,cache_version) + .then((json)=>{ + if(json) { + // also change load_replies! + json.data=json.data + .sort((a,b)=>{ + return parseInt(a.timestamp,10)-parseInt(b.timestamp,10); + }) + .map((info)=>{ + info._display_color=color_picker.get(info.name); + info.variant={}; + return info; + }); + + return json; + } + else + return API.load_replies(pid,token,color_picker,cache_version); + }); + }, + set_attention: (pid,attention,token)=>{ let data=new URLSearchParams(); data.append('user_token',token);