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
|
|
|
|
2018-01-24 02:15:14 +00:00
|
|
|
import QuickLRU from 'quick-lru'
|
|
|
|
|
|
|
|
const statusesCache = new QuickLRU({maxSize: 100})
|
2018-01-25 08:01:56 +00:00
|
|
|
const accountsCache = new QuickLRU({maxSize: 50})
|
2018-01-25 16:44:10 +00:00
|
|
|
const metaCache = new QuickLRU({maxSize: 20})
|
2018-01-24 02:15:14 +00:00
|
|
|
|
|
|
|
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
|
2018-01-25 16:44:10 +00:00
|
|
|
},
|
|
|
|
meta: {
|
|
|
|
cache: accountsCache,
|
|
|
|
hits: 0,
|
|
|
|
misses: 0
|
2018-01-25 08:01:56 +00:00
|
|
|
}
|
2018-01-24 02:15:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-25 16:44:10 +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) {
|
2018-01-24 02:15:14 +00:00
|
|
|
for (let status of statuses) {
|
|
|
|
statusesCache.set(status.id, status)
|
2018-01-25 08:01:56 +00:00
|
|
|
accountsCache.set(status.account.id, status.account)
|
|
|
|
if (status.reblog) {
|
|
|
|
accountsCache.set(status.reblog.account.id, status.reblog.account)
|
|
|
|
}
|
2018-01-24 02:15:14 +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
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-01-25 16:44:10 +00:00
|
|
|
export async function getStatus(instanceName, statusId) {
|
|
|
|
if (statusesCache.has(statusId)) {
|
|
|
|
if (process.browser && process.env.NODE_ENV !== 'production') {
|
|
|
|
window.cacheStats.statuses.hits++
|
|
|
|
}
|
|
|
|
return statusesCache.get(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)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
statusesCache.set(statusId, result)
|
|
|
|
if (process.browser && process.env.NODE_ENV !== 'production') {
|
|
|
|
window.cacheStats.statuses.misses++
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// meta
|
|
|
|
//
|
|
|
|
|
|
|
|
async function getMetaProperty(instanceName, key) {
|
|
|
|
if (metaCache.has(key)) {
|
|
|
|
if (process.browser && process.env.NODE_ENV !== 'production') {
|
|
|
|
window.cacheStats.meta.hits++
|
|
|
|
}
|
|
|
|
return metaCache.get(key)
|
|
|
|
}
|
2018-01-23 17:03:31 +00:00
|
|
|
const db = await getDatabase(instanceName)
|
2018-01-25 16:44:10 +00:00
|
|
|
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-25 16:44:10 +00:00
|
|
|
metaCache.set(key, result)
|
|
|
|
if (process.browser && process.env.NODE_ENV !== 'production') {
|
|
|
|
window.cacheStats.meta.misses++
|
|
|
|
}
|
|
|
|
return result
|
2018-01-22 02:36:40 +00:00
|
|
|
}
|
|
|
|
|
2018-01-25 16:44:10 +00:00
|
|
|
async function setMetaProperty(instanceName, key, value) {
|
|
|
|
metaCache.set(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({
|
2018-01-25 16:44:10 +00:00
|
|
|
key: key,
|
|
|
|
value: value
|
2018-01-22 02:36:40 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-01-25 16:44:10 +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-25 08:01:56 +00:00
|
|
|
if (accountsCache.has(accountId)) {
|
|
|
|
if (process.browser && process.env.NODE_ENV !== 'production') {
|
|
|
|
window.cacheStats.accounts.hits++
|
|
|
|
}
|
|
|
|
return accountsCache.get(accountId)
|
|
|
|
}
|
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-25 16:44:10 +00:00
|
|
|
//
|
|
|
|
// lifecycle
|
|
|
|
//
|
|
|
|
|
2018-01-23 17:03:31 +00:00
|
|
|
export async function clearDatabaseForInstance(instanceName) {
|
|
|
|
await deleteDatabase(instanceName)
|
2018-01-22 02:36:40 +00:00
|
|
|
}
|