implement database cleanup on logout

This commit is contained in:
Nolan Lawson 2018-01-21 18:36:40 -08:00
parent 8e81926076
commit 0e229bedff
10 changed files with 127 additions and 93 deletions

View File

@ -0,0 +1,2 @@
export const openReqs = {}
export const databaseCache = {}

View File

@ -1,9 +1,5 @@
import worker from 'workerize-loader!./databaseInsideWorker' import worker from 'workerize-loader!./databaseCore'
const database = process.browser && worker()
import * as databaseInsideWorker from './databaseInsideWorker'
// workerize-loader causes weirdness during development
let database = process.browser && process.env.NODE_ENV === 'production' ? worker() : databaseInsideWorker
export { export {
database database

View File

@ -0,0 +1,62 @@
import { META_STORE, getMetaDatabase } from './meta'
import { cleanupOldStatuses } from './cleanupTimelines'
import { TIMELINE_STORE, getTimelineDatabase } from './timelines'
import { toReversePaddedBigInt, transformStatusForStorage, dbPromise, deleteDbPromise } from './utils'
import { getKnownDbsForInstance, deleteInstanceFromKnownDbs } from './knownDbs'
export async function getTimeline(instanceName, timeline, maxId = null, limit = 20) {
const db = await getTimelineDatabase(instanceName, timeline)
return await dbPromise(db, TIMELINE_STORE, 'readonly', (store, callback) => {
const index = store.index('pinafore_id_as_negative_big_int')
let sinceAsNegativeBigInt = maxId ? toReversePaddedBigInt(maxId) : null
let query = sinceAsNegativeBigInt ? IDBKeyRange.lowerBound(sinceAsNegativeBigInt, false) : null
index.getAll(query, limit).onsuccess = (e) => {
callback(e.target.result)
}
})
}
export async function insertStatuses(instanceName, timeline, statuses) {
const db = await getTimelineDatabase(instanceName, timeline)
await dbPromise(db, TIMELINE_STORE, 'readwrite', (store) => {
for (let status of statuses) {
store.put(transformStatusForStorage(status))
}
})
/* no await */ cleanupOldStatuses()
}
export async function getInstanceVerifyCredentials(instanceName) {
const db = await getMetaDatabase(instanceName)
return await dbPromise(db, META_STORE, 'readonly', (store, callback) => {
store.get('verifyCredentials').onsuccess = (e) => {
callback(e.target.result && e.target.result.value)
}
})
}
export async function setInstanceVerifyCredentials(instanceName, verifyCredentials) {
const db = await getMetaDatabase(instanceName)
return await dbPromise(db, META_STORE, 'readwrite', (store) => {
store.put({
key: 'verifyCredentials',
value: verifyCredentials
})
})
}
export async function clearDatabasesForInstance(instanceName) {
console.log('clearDatabasesForInstance', instanceName)
const knownDbsForInstance = await getKnownDbsForInstance(instanceName)
for (let knownDb of knownDbsForInstance) {
let { dbName } = knownDb
try {
await deleteDbPromise(dbName)
console.error(`deleted database ${dbName}`)
} catch (e) {
console.error(`failed to delete database ${dbName}`)
}
}
await deleteInstanceFromKnownDbs(instanceName)
}

View File

@ -1,65 +0,0 @@
import { META_STORE, getMetaDatabase } from './meta'
import { cleanupOldStatuses } from './cleanupTimelines'
import { TIMELINE_STORE, getTimelineDatabase } from './timelines'
import { toReversePaddedBigInt, transformStatusForStorage } from './utils'
export async function getTimeline(instanceName, timeline, maxId = null, limit = 20) {
const db = await getTimelineDatabase(instanceName, timeline)
return await new Promise((resolve, reject) => {
const tx = db.transaction(TIMELINE_STORE, 'readonly')
const store = tx.objectStore(TIMELINE_STORE)
const index = store.index('pinafore_id_as_negative_big_int')
let sinceAsNegativeBigInt = maxId ? toReversePaddedBigInt(maxId) : null
let query = sinceAsNegativeBigInt ? IDBKeyRange.lowerBound(sinceAsNegativeBigInt, false) : null
let res
index.getAll(query, limit).onsuccess = (e) => {
res = e.target.result
}
tx.oncomplete = () => resolve(res)
tx.onerror = () => reject(tx.error.name + ' ' + tx.error.message)
})
}
export async function insertStatuses(instanceName, timeline, statuses) {
const db = await getTimelineDatabase(instanceName, timeline)
await new Promise((resolve, reject) => {
const tx = db.transaction(TIMELINE_STORE, 'readwrite')
const store = tx.objectStore(TIMELINE_STORE)
for (let status of statuses) {
store.put(transformStatusForStorage(status))
}
tx.oncomplete = () => resolve()
tx.onerror = () => reject(tx.error.name + ' ' + tx.error.message)
})
/* no await */ cleanupOldStatuses()
}
export async function setInstanceVerifyCredentials(instanceName, verifyCredentials) {
const db = await getMetaDatabase(instanceName)
return await new Promise((resolve, reject) => {
const tx = db.transaction(META_STORE, 'readwrite')
const store = tx.objectStore(META_STORE)
store.put({
key: 'verifyCredentials',
value: verifyCredentials
})
tx.oncomplete = () => resolve()
tx.onerror = () => reject(tx.error.name + ' ' + tx.error.message)
})
}
export async function getInstanceVerifyCredentials(instanceName, verifyCredentials) {
const db = await getMetaDatabase(instanceName)
return await new Promise((resolve, reject) => {
const tx = db.transaction(META_STORE, 'readwrite')
const store = tx.objectStore(META_STORE)
let res
store.get('verifyCredentials').onsuccess = (e) => {
res = e.target.result && e.target.result.value
}
tx.oncomplete = () => resolve(res)
tx.onerror = () => reject(tx.error.name + ' ' + tx.error.message)
})
}

View File

@ -1,7 +1,7 @@
import keyval from "idb-keyval" import keyval from "idb-keyval"
export async function addKnownDb(instanceName, type, dbName) { export async function addKnownDb(instanceName, type, dbName) {
let knownDbs = (await keyval.get('known_dbs')) || {} let knownDbs = (await getKnownDbs())
if (!knownDbs[instanceName]) { if (!knownDbs[instanceName]) {
knownDbs[instanceName] = [] knownDbs[instanceName] = []
} }
@ -18,4 +18,10 @@ export async function getKnownDbs() {
export async function getKnownDbsForInstance(instanceName) { export async function getKnownDbsForInstance(instanceName) {
let knownDbs = await getKnownDbs() let knownDbs = await getKnownDbs()
return knownDbs[instanceName] || [] return knownDbs[instanceName] || []
}
export async function deleteInstanceFromKnownDbs(instanceName) {
let knownDbs = await getKnownDbs()
delete knownDbs[instanceName]
await keyval.set('known_dbs', knownDbs)
} }

View File

@ -1,20 +1,19 @@
import { addKnownDb } from './knownDbs' import { addKnownDb } from './knownDbs'
import { openReqs, databaseCache } from './cache'
const databaseCache = {}
export const META_STORE = 'meta' export const META_STORE = 'meta'
export function getMetaDatabase(instanceName) { export function getMetaDatabase(instanceName) {
const key = `${instanceName}_${META_STORE}` const dbName = `${instanceName}_${META_STORE}`
if (databaseCache[key]) { if (databaseCache[dbName]) {
return Promise.resolve(databaseCache[key]) return Promise.resolve(databaseCache[dbName])
} }
let dbName = key
addKnownDb(instanceName, 'meta', dbName) addKnownDb(instanceName, 'meta', dbName)
databaseCache[key] = new Promise((resolve, reject) => { databaseCache[dbName] = new Promise((resolve, reject) => {
let req = indexedDB.open(dbName, 1) let req = indexedDB.open(dbName, 1)
openReqs[dbName] = req
req.onerror = reject req.onerror = reject
req.onblocked = () => { req.onblocked = () => {
console.log('idb blocked') console.log('idb blocked')
@ -25,5 +24,5 @@ export function getMetaDatabase(instanceName) {
} }
req.onsuccess = () => resolve(req.result) req.onsuccess = () => resolve(req.result)
}) })
return databaseCache[key] return databaseCache[dbName]
} }

View File

@ -1,6 +1,6 @@
import { addKnownDb } from './knownDbs' import { addKnownDb } from './knownDbs'
import { openReqs, databaseCache } from './cache'
const databaseCache = {}
export const TIMELINE_STORE = 'statuses' export const TIMELINE_STORE = 'statuses'
export function createTimelineDbName(instanceName, timeline) { export function createTimelineDbName(instanceName, timeline) {
@ -8,17 +8,17 @@ export function createTimelineDbName(instanceName, timeline) {
} }
export function getTimelineDatabase(instanceName, timeline) { export function getTimelineDatabase(instanceName, timeline) {
const key = `${instanceName}_${timeline}`
if (databaseCache[key]) {
return Promise.resolve(databaseCache[key])
}
let dbName = createTimelineDbName(instanceName, timeline) let dbName = createTimelineDbName(instanceName, timeline)
if (databaseCache[dbName]) {
return Promise.resolve(databaseCache[dbName])
}
addKnownDb(instanceName, 'timeline', dbName) addKnownDb(instanceName, 'timeline', dbName)
databaseCache[key] = new Promise((resolve, reject) => { databaseCache[dbName] = new Promise((resolve, reject) => {
let req = indexedDB.open(dbName, 1) let req = indexedDB.open(dbName, 1)
openReqs[dbName] = req
req.onerror = reject req.onerror = reject
req.onblocked = () => { req.onblocked = () => {
console.log('idb blocked') console.log('idb blocked')
@ -32,5 +32,5 @@ export function getTimelineDatabase(instanceName, timeline) {
} }
req.onsuccess = () => resolve(req.result) req.onsuccess = () => resolve(req.result)
}) })
return databaseCache[key] return databaseCache[dbName]
} }

View File

@ -1,5 +1,6 @@
import cloneDeep from 'lodash/cloneDeep' import cloneDeep from 'lodash/cloneDeep'
import padStart from 'lodash/padStart' import padStart from 'lodash/padStart'
import { databaseCache, openReqs } from './cache'
export function toPaddedBigInt (id) { export function toPaddedBigInt (id) {
return padStart(id, 30, '0') return padStart(id, 30, '0')
@ -19,4 +20,33 @@ export function transformStatusForStorage (status) {
status.pinafore_id_as_negative_big_int = toReversePaddedBigInt(status.id) status.pinafore_id_as_negative_big_int = toReversePaddedBigInt(status.id)
status.pinafore_stale = true status.pinafore_stale = true
return status return status
}
export async function dbPromise(db, storeName, readOnlyOrReadWrite, cb) {
return await new Promise((resolve, reject) => {
const tx = db.transaction(storeName, readOnlyOrReadWrite)
const store = tx.objectStore(storeName)
let res
cb(store, (result) => {
res = result
})
tx.oncomplete = () => resolve(res)
tx.onerror = () => reject(tx.error.name + ' ' + tx.error.message)
})
}
export function deleteDbPromise(dbName) {
return new Promise((resolve, reject) => {
// close any open requests
let openReq = openReqs[dbName];
if (openReq && openReq.result) {
openReq.result.close()
}
delete openReqs[dbName]
delete databaseCache[dbName]
let req = indexedDB.deleteDatabase(dbName)
req.onsuccess = () => resolve()
req.onerror = () => reject(req.error.name + ' ' + req.error.message)
})
} }

View File

@ -30,11 +30,11 @@
<form class="instance-actions" aria-label="Switch to or log out of this instance"> <form class="instance-actions" aria-label="Switch to or log out of this instance">
{{#if $loggedInInstancesInOrder.length > 1}} {{#if $loggedInInstancesInOrder.length > 1}}
<button class="primary" disabled="{{$currentInstance === params.instanceName}}" <button class="primary" disabled="{{$currentInstance === params.instanceName}}"
on:click="onSwitchToThisInstance()"> on:click="onSwitchToThisInstance(event)">
{{$currentInstance === params.instanceName ? 'This is your current instance' : 'Switch to this instance'}} {{$currentInstance === params.instanceName ? 'This is your current instance' : 'Switch to this instance'}}
</button> </button>
{{/if}} {{/if}}
<button on:click="onLogOut()">Log out</button> <button on:click="onLogOut(event)">Log out</button>
</form> </form>
{{/if}} {{/if}}
</SettingsLayout> </SettingsLayout>
@ -145,7 +145,8 @@
switchToTheme(newTheme) switchToTheme(newTheme)
} }
}, },
onSwitchToThisInstance() { onSwitchToThisInstance(e) {
e.preventDefault()
let instanceName = this.get('params').instanceName let instanceName = this.get('params').instanceName
let instanceThemes = this.store.get('instanceThemes') let instanceThemes = this.store.get('instanceThemes')
this.store.set({ this.store.set({
@ -154,7 +155,8 @@
this.store.save() this.store.save()
switchToTheme(instanceThemes[instanceName]) switchToTheme(instanceThemes[instanceName])
}, },
onLogOut() { onLogOut(e) {
e.preventDefault()
let loggedInInstances = this.store.get('loggedInInstances') let loggedInInstances = this.store.get('loggedInInstances')
let instanceThemes = this.store.get('instanceThemes') let instanceThemes = this.store.get('instanceThemes')
let loggedInInstancesInOrder = this.store.get('loggedInInstancesInOrder') let loggedInInstancesInOrder = this.store.get('loggedInInstancesInOrder')
@ -173,6 +175,7 @@
currentInstance: newInstance currentInstance: newInstance
}) })
this.store.save() this.store.save()
database.clearDatabasesForInstance(instanceName)
switchToTheme(instanceThemes[newInstance] || 'default') switchToTheme(instanceThemes[newInstance] || 'default')
toast.say(`Logged out of ${instanceName}`) toast.say(`Logged out of ${instanceName}`)
goto('/settings/instances') goto('/settings/instances')

View File

@ -47,7 +47,8 @@ body.offline,body.theme-hotpants.offline,body.theme-majesty.offline,body.theme-o
offline: "#999999" offline: "#999999"
} }
if (localStorage.store_currentInstance && localStorage.store_instanceThemes) { if (localStorage.store_currentInstance && localStorage.store_instanceThemes) {
let theme = JSON.parse(localStorage.store_instanceThemes)[JSON.parse(localStorage.store_currentInstance)] let safeParse = (str) => str === 'undefined' ? undefined : JSON.parse(str)
let theme = safeParse(localStorage.store_instanceThemes)[safeParse(localStorage.store_currentInstance)]
if (theme !== 'default') { if (theme !== 'default') {
document.body.classList.add(`theme-${theme}`) document.body.classList.add(`theme-${theme}`)
let link = document.createElement('link') let link = document.createElement('link')