pinafore/routes/_utils/database/databaseCore.js

242 lines
6.7 KiB
JavaScript
Raw Normal View History

2018-01-23 17:03:31 +00:00
import {
toReversePaddedBigInt
} from './utils'
import {
getDatabase,
dbPromise,
deleteDatabase,
} from './databaseLifecycle'
import {
META_STORE,
TIMELINE_STORE,
2018-01-23 17:21:21 +00:00
STATUSES_STORE, ACCOUNTS_STORE
2018-01-23 17:03:31 +00:00
} from './constants'
2018-01-22 02:36:40 +00:00
import QuickLRU from 'quick-lru'
2018-01-27 22:55:46 +00:00
const statusesCache = {
maxSize: 100,
caches: {}
}
const accountsCache = {
maxSize: 50,
caches: {}
}
const metaCache = {
maxSize: 20,
caches: {}
}
if (process.browser && process.env.NODE_ENV !== 'production') {
window.cacheStats = {
2018-01-25 08:01:56 +00:00
statuses: {
cache: statusesCache,
hits: 0,
misses: 0
},
accounts: {
cache: accountsCache,
hits: 0,
misses: 0
},
meta: {
cache: accountsCache,
hits: 0,
misses: 0
2018-01-25 08:01:56 +00:00
}
}
}
2018-01-27 22:55:46 +00:00
function clearCache(cache, instanceName) {
delete cache.caches[instanceName]
}
function getOrCreateInstanceCache(cache, instanceName) {
let cached = cache.caches[instanceName]
if (!cached) {
cached = cache.caches[instanceName] = new QuickLRU({maxSize: cache.maxSize})
}
return cached
}
2018-01-27 22:45:51 +00:00
function setInCache(cache, instanceName, key, value) {
2018-01-27 22:55:46 +00:00
let instanceCache = getOrCreateInstanceCache(cache, instanceName)
return instanceCache.set(key, value)
2018-01-27 22:45:51 +00:00
}
function getInCache(cache, instanceName, key) {
2018-01-27 22:55:46 +00:00
let instanceCache = getOrCreateInstanceCache(cache, instanceName)
return instanceCache.get(key)
2018-01-27 22:45:51 +00:00
}
function hasInCache(cache, instanceName, key) {
2018-01-27 22:55:46 +00:00
let instanceCache = getOrCreateInstanceCache(cache, instanceName)
return instanceCache.has(key)
2018-01-27 22:45:51 +00:00
}
//
// timelines/statuses
//
2018-01-22 02:36:40 +00:00
export async function getTimeline(instanceName, timeline, maxId = null, limit = 20) {
2018-01-23 17:03:31 +00:00
const db = await getDatabase(instanceName, timeline)
return await dbPromise(db, [TIMELINE_STORE, STATUSES_STORE], 'readonly', (stores, callback) => {
let [ timelineStore, statusesStore ] = stores
let negBigInt = maxId && toReversePaddedBigInt(maxId)
let start = negBigInt ? (timeline + '\u0000' + negBigInt) : (timeline + '\u0000')
let end = timeline + '\u0000\uffff'
let query = IDBKeyRange.bound(start, end, false, false)
timelineStore.getAll(query, limit).onsuccess = e => {
let timelineResults = e.target.result
let res = new Array(timelineResults.length)
timelineResults.forEach((timelineResult, i) => {
statusesStore.get(timelineResult.statusId).onsuccess = e => {
res[i] = e.target.result
}
})
callback(res)
2018-01-22 02:36:40 +00:00
}
})
}
export async function insertStatuses(instanceName, timeline, statuses) {
for (let status of statuses) {
2018-01-27 22:45:51 +00:00
setInCache(statusesCache, instanceName, status.id, status)
setInCache(accountsCache, instanceName, status.account.id, status.account)
2018-01-25 08:01:56 +00:00
if (status.reblog) {
2018-01-27 22:45:51 +00:00
setInCache(accountsCache, instanceName, status.reblog.account.id, status.reblog.account)
2018-01-25 08:01:56 +00:00
}
}
2018-01-23 17:03:31 +00:00
const db = await getDatabase(instanceName, timeline)
2018-01-23 17:21:21 +00:00
await dbPromise(db, [TIMELINE_STORE, STATUSES_STORE, ACCOUNTS_STORE], 'readwrite', (stores) => {
let [ timelineStore, statusesStore, accountsStore ] = stores
2018-01-22 02:36:40 +00:00
for (let status of statuses) {
2018-01-23 17:03:31 +00:00
statusesStore.put(status)
// reverse chronological order, prefixed by timeline
timelineStore.put({
2018-01-23 17:21:21 +00:00
id: (timeline + '\u0000' + toReversePaddedBigInt(status.id)),
2018-01-23 17:03:31 +00:00
statusId: status.id
})
2018-01-23 17:21:21 +00:00
accountsStore.put(status.account)
if (status.reblog) {
accountsStore.put(status.reblog.account)
}
2018-01-22 02:36:40 +00:00
}
})
}
export async function getStatus(instanceName, statusId) {
2018-01-27 22:45:51 +00:00
if (hasInCache(statusesCache, instanceName, statusId)) {
if (process.browser && process.env.NODE_ENV !== 'production') {
window.cacheStats.statuses.hits++
}
2018-01-27 22:45:51 +00:00
return getInCache(statusesCache, instanceName, statusId)
}
const db = await getDatabase(instanceName)
let result = await dbPromise(db, STATUSES_STORE, 'readonly', (store, callback) => {
store.get(statusId).onsuccess = (e) => {
callback(e.target.result && e.target.result)
}
})
2018-01-27 22:45:51 +00:00
setInCache(statusesCache, instanceName, statusId, result)
if (process.browser && process.env.NODE_ENV !== 'production') {
window.cacheStats.statuses.misses++
}
return result
}
//
// meta
//
async function getMetaProperty(instanceName, key) {
2018-01-27 22:45:51 +00:00
if (hasInCache(metaCache, instanceName, key)) {
if (process.browser && process.env.NODE_ENV !== 'production') {
window.cacheStats.meta.hits++
}
2018-01-27 22:45:51 +00:00
return getInCache(metaCache, instanceName, key)
}
2018-01-23 17:03:31 +00:00
const db = await getDatabase(instanceName)
let result = await dbPromise(db, META_STORE, 'readonly', (store, callback) => {
store.get(key).onsuccess = (e) => {
2018-01-22 02:36:40 +00:00
callback(e.target.result && e.target.result.value)
}
})
2018-01-27 22:45:51 +00:00
setInCache(metaCache, instanceName, key, result)
if (process.browser && process.env.NODE_ENV !== 'production') {
window.cacheStats.meta.misses++
}
return result
2018-01-22 02:36:40 +00:00
}
async function setMetaProperty(instanceName, key, value) {
2018-01-27 22:45:51 +00:00
setInCache(metaCache, instanceName, key, value)
2018-01-23 17:03:31 +00:00
const db = await getDatabase(instanceName)
2018-01-22 02:36:40 +00:00
return await dbPromise(db, META_STORE, 'readwrite', (store) => {
store.put({
key: key,
value: value
2018-01-22 02:36:40 +00:00
})
})
}
export async function getInstanceVerifyCredentials(instanceName) {
return await getMetaProperty(instanceName, 'verifyCredentials')
}
export async function setInstanceVerifyCredentials(instanceName, value) {
return await setMetaProperty(instanceName, 'verifyCredentials', value)
}
export async function getInstanceInfo(instanceName) {
return await getMetaProperty(instanceName, 'instance')
}
export async function setInstanceInfo(instanceName, value) {
return await setMetaProperty(instanceName, 'instance', value)
}
//
// accounts
//
2018-01-23 17:21:21 +00:00
export async function getAccount(instanceName, accountId) {
2018-01-27 22:45:51 +00:00
if (hasInCache(accountsCache, instanceName, accountId)) {
2018-01-25 08:01:56 +00:00
if (process.browser && process.env.NODE_ENV !== 'production') {
window.cacheStats.accounts.hits++
}
2018-01-27 22:45:51 +00:00
return getInCache(accountsCache, instanceName, accountId)
2018-01-25 08:01:56 +00:00
}
2018-01-23 17:21:21 +00:00
const db = await getDatabase(instanceName)
2018-01-25 08:01:56 +00:00
let result = await dbPromise(db, ACCOUNTS_STORE, 'readonly', (store, callback) => {
2018-01-23 17:21:21 +00:00
store.get(accountId).onsuccess = (e) => {
callback(e.target.result && e.target.result)
}
})
2018-01-25 08:01:56 +00:00
if (process.browser && process.env.NODE_ENV !== 'production') {
window.cacheStats.accounts.misses++
}
return result
2018-01-23 17:21:21 +00:00
}
2018-01-28 04:23:52 +00:00
export async function setAccount(instanceName, account) {
setInCache(accountsCache, instanceName, account.id, account)
const db = await getDatabase(instanceName)
return await dbPromise(db, ACCOUNTS_STORE, 'readwrite', (store) => {
store.put(account)
})
}
//
// lifecycle
//
2018-01-23 17:03:31 +00:00
export async function clearDatabaseForInstance(instanceName) {
2018-01-27 22:55:46 +00:00
clearCache(statusesCache, instanceName)
clearCache(accountsCache, instanceName)
clearCache(metaCache, instanceName)
2018-01-23 17:03:31 +00:00
await deleteDatabase(instanceName)
2018-01-22 02:36:40 +00:00
}