refactor database
This commit is contained in:
parent
924e803d16
commit
4b04cc92f1
|
@ -20,13 +20,11 @@
|
||||||
import LoadingFooter from './LoadingFooter.html'
|
import LoadingFooter from './LoadingFooter.html'
|
||||||
import VirtualList from './VirtualList.html'
|
import VirtualList from './VirtualList.html'
|
||||||
import { splice, push } from 'svelte-extras'
|
import { splice, push } from 'svelte-extras'
|
||||||
import worker from 'workerize-loader!../_utils/database/database'
|
|
||||||
import { mergeStatuses } from '../_utils/statuses'
|
import { mergeStatuses } from '../_utils/statuses'
|
||||||
import { mark, stop } from '../_utils/marks'
|
import { mark, stop } from '../_utils/marks'
|
||||||
import { timelines } from '../_static/timelines'
|
import { timelines } from '../_static/timelines'
|
||||||
import { toast } from '../_utils/toast'
|
import { toast } from '../_utils/toast'
|
||||||
|
import { database } from '../_utils/database/database'
|
||||||
const database = worker()
|
|
||||||
|
|
||||||
const FETCH_LIMIT = 20
|
const FETCH_LIMIT = 20
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import keyval from "idb-keyval"
|
import { getKnownDbs } from './knownDbs'
|
||||||
import debounce from 'lodash/debounce'
|
import debounce from 'lodash/debounce'
|
||||||
import { OBJECT_STORE, getDatabase } from './shared'
|
import { TIMELINE_STORE, getTimelineDatabase } from './timelines'
|
||||||
|
|
||||||
const MAX_NUM_STORED_STATUSES = 1000
|
const MAX_NUM_STORED_STATUSES = 1000
|
||||||
const CLEANUP_INTERVAL = 60000
|
const CLEANUP_INTERVAL = 60000
|
||||||
|
|
||||||
async function cleanup(instanceName, timeline) {
|
async function cleanup(instanceName, timeline) {
|
||||||
const db = await getDatabase(instanceName, timeline)
|
const db = await getTimelineDatabase(instanceName, timeline)
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
const tx = db.transaction(OBJECT_STORE, 'readwrite')
|
const tx = db.transaction(TIMELINE_STORE, 'readwrite')
|
||||||
const store = tx.objectStore(OBJECT_STORE)
|
const store = tx.objectStore(TIMELINE_STORE)
|
||||||
const index = store.index('pinafore_id_as_negative_big_int')
|
const index = store.index('pinafore_id_as_negative_big_int')
|
||||||
|
|
||||||
store.count().onsuccess = (e) => {
|
store.count().onsuccess = (e) => {
|
||||||
|
@ -37,11 +37,18 @@ export const cleanupOldStatuses = debounce(async () => {
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
console.log('cleanupOldStatuses')
|
console.log('cleanupOldStatuses')
|
||||||
}
|
}
|
||||||
let knownDbs = (await keyval.get('known_dbs')) || {}
|
|
||||||
let dbNames = Object.keys(knownDbs)
|
let knownDbs = await getKnownDbs()
|
||||||
for (let dbName of dbNames) {
|
let instanceNames = Object.keys(knownDbs)
|
||||||
let [ instanceName, timeline ] = knownDbs[dbName]
|
for (let instanceName of instanceNames) {
|
||||||
await cleanup(instanceName, timeline)
|
let knownDbsForInstance = knownDbs[instanceName] || []
|
||||||
|
for (let knownDb of knownDbsForInstance) {
|
||||||
|
let {type, dbName} = knownDb
|
||||||
|
if (type !== 'timeline') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
await cleanup(instanceName, dbName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
console.log('done cleanupOldStatuses')
|
console.log('done cleanupOldStatuses')
|
|
@ -1,36 +1,10 @@
|
||||||
import { cleanupOldStatuses } from './cleanup'
|
import worker from 'workerize-loader!./databaseInsideWorker'
|
||||||
import { OBJECT_STORE, getDatabase } from './shared'
|
|
||||||
import { toReversePaddedBigInt, transformStatusForStorage } from './utils'
|
|
||||||
|
|
||||||
export async function getTimeline(instanceName, timeline, maxId = null, limit = 20) {
|
import * as databaseInsideWorker from './databaseInsideWorker'
|
||||||
const db = await getDatabase(instanceName, timeline)
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
const tx = db.transaction(OBJECT_STORE, 'readonly')
|
|
||||||
const store = tx.objectStore(OBJECT_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
|
// workerize-loader causes weirdness during development
|
||||||
index.getAll(query, limit).onsuccess = (e) => {
|
let database = process.browser && process.env.NODE_ENV === 'production' ? worker() : databaseInsideWorker
|
||||||
res = e.target.result
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.oncomplete = () => resolve(res)
|
export {
|
||||||
tx.onerror = () => reject(tx.error.name + ' ' + tx.error.message)
|
database
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function insertStatuses(instanceName, timeline, statuses) {
|
|
||||||
cleanupOldStatuses()
|
|
||||||
const db = await getDatabase(instanceName, timeline)
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
const tx = db.transaction(OBJECT_STORE, 'readwrite')
|
|
||||||
const store = tx.objectStore(OBJECT_STORE)
|
|
||||||
for (let status of statuses) {
|
|
||||||
store.put(transformStatusForStorage(status))
|
|
||||||
}
|
|
||||||
tx.oncomplete = () => resolve()
|
|
||||||
tx.onerror = () => reject(tx.error.name + ' ' + tx.error.message)
|
|
||||||
})
|
|
||||||
}
|
}
|
65
routes/_utils/database/databaseInsideWorker.js
Normal file
65
routes/_utils/database/databaseInsideWorker.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
21
routes/_utils/database/knownDbs.js
Normal file
21
routes/_utils/database/knownDbs.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import keyval from "idb-keyval"
|
||||||
|
|
||||||
|
export async function addKnownDb(instanceName, type, dbName) {
|
||||||
|
let knownDbs = (await keyval.get('known_dbs')) || {}
|
||||||
|
if (!knownDbs[instanceName]) {
|
||||||
|
knownDbs[instanceName] = []
|
||||||
|
}
|
||||||
|
if (!knownDbs[instanceName].some(db => db.type === type && db.dbName === dbName)) {
|
||||||
|
knownDbs[instanceName].push({type, dbName})
|
||||||
|
}
|
||||||
|
await keyval.set('known_dbs', knownDbs)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getKnownDbs() {
|
||||||
|
return (await keyval.get('known_dbs')) || {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getKnownDbsForInstance(instanceName) {
|
||||||
|
let knownDbs = await getKnownDbs()
|
||||||
|
return knownDbs[instanceName] || []
|
||||||
|
}
|
29
routes/_utils/database/meta.js
Normal file
29
routes/_utils/database/meta.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { addKnownDb } from './knownDbs'
|
||||||
|
|
||||||
|
const databaseCache = {}
|
||||||
|
export const META_STORE = 'meta'
|
||||||
|
|
||||||
|
export function getMetaDatabase(instanceName) {
|
||||||
|
const key = `${instanceName}_${META_STORE}`
|
||||||
|
if (databaseCache[key]) {
|
||||||
|
return Promise.resolve(databaseCache[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
let dbName = key
|
||||||
|
|
||||||
|
addKnownDb(instanceName, 'meta', dbName)
|
||||||
|
|
||||||
|
databaseCache[key] = new Promise((resolve, reject) => {
|
||||||
|
let req = indexedDB.open(dbName, 1)
|
||||||
|
req.onerror = reject
|
||||||
|
req.onblocked = () => {
|
||||||
|
console.log('idb blocked')
|
||||||
|
}
|
||||||
|
req.onupgradeneeded = () => {
|
||||||
|
let db = req.result;
|
||||||
|
db.createObjectStore(META_STORE, {keyPath: 'key'})
|
||||||
|
}
|
||||||
|
req.onsuccess = () => resolve(req.result)
|
||||||
|
})
|
||||||
|
return databaseCache[key]
|
||||||
|
}
|
|
@ -1,25 +1,21 @@
|
||||||
import keyval from "idb-keyval"
|
import { addKnownDb } from './knownDbs'
|
||||||
|
|
||||||
const databaseCache = {}
|
const databaseCache = {}
|
||||||
export const OBJECT_STORE = 'statuses'
|
export const TIMELINE_STORE = 'statuses'
|
||||||
|
|
||||||
export function createDbName(instanceName, timeline) {
|
export function createTimelineDbName(instanceName, timeline) {
|
||||||
return `${OBJECT_STORE}_${instanceName}_${timeline}`
|
return `${instanceName}_timeline_${timeline}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDatabase(instanceName, timeline) {
|
export function getTimelineDatabase(instanceName, timeline) {
|
||||||
const key = `${instanceName}_${timeline}`
|
const key = `${instanceName}_${timeline}`
|
||||||
if (databaseCache[key]) {
|
if (databaseCache[key]) {
|
||||||
return Promise.resolve(databaseCache[key])
|
return Promise.resolve(databaseCache[key])
|
||||||
}
|
}
|
||||||
|
|
||||||
let dbName = createDbName(instanceName, timeline)
|
let dbName = createTimelineDbName(instanceName, timeline)
|
||||||
|
|
||||||
keyval.get('known_dbs').then(knownDbs => {
|
addKnownDb(instanceName, 'timeline', dbName)
|
||||||
knownDbs = knownDbs || {}
|
|
||||||
knownDbs[dbName] = [instanceName, timeline]
|
|
||||||
keyval.set('known_dbs', knownDbs)
|
|
||||||
})
|
|
||||||
|
|
||||||
databaseCache[key] = new Promise((resolve, reject) => {
|
databaseCache[key] = new Promise((resolve, reject) => {
|
||||||
let req = indexedDB.open(dbName, 1)
|
let req = indexedDB.open(dbName, 1)
|
||||||
|
@ -29,7 +25,7 @@ export function getDatabase(instanceName, timeline) {
|
||||||
}
|
}
|
||||||
req.onupgradeneeded = () => {
|
req.onupgradeneeded = () => {
|
||||||
let db = req.result;
|
let db = req.result;
|
||||||
let oStore = db.createObjectStore(OBJECT_STORE, {
|
let oStore = db.createObjectStore(TIMELINE_STORE, {
|
||||||
keyPath: 'id'
|
keyPath: 'id'
|
||||||
})
|
})
|
||||||
oStore.createIndex('pinafore_id_as_negative_big_int', 'pinafore_id_as_negative_big_int')
|
oStore.createIndex('pinafore_id_as_negative_big_int', 'pinafore_id_as_negative_big_int')
|
|
@ -1,7 +1,7 @@
|
||||||
import { get } from '../ajax'
|
import { get } from '../ajax'
|
||||||
import { basename } from './utils'
|
import { basename } from './utils'
|
||||||
|
|
||||||
export function getThisUserAccount(instanceName, accessToken) {
|
export function getVerifyCredentials(instanceName, accessToken) {
|
||||||
let url = `${basename(instanceName)}/api/v1/accounts/verify_credentials`
|
let url = `${basename(instanceName)}/api/v1/accounts/verify_credentials`
|
||||||
return get(url, {
|
return get(url, {
|
||||||
'Authorization': `Bearer ${accessToken}`
|
'Authorization': `Bearer ${accessToken}`
|
||||||
|
|
|
@ -6,14 +6,14 @@
|
||||||
<SettingsLayout page='settings/instances/{{params.instanceName}}' label="{{params.instanceName}}">
|
<SettingsLayout page='settings/instances/{{params.instanceName}}' label="{{params.instanceName}}">
|
||||||
<h1 class="instance-name-h1">{{params.instanceName}}</h1>
|
<h1 class="instance-name-h1">{{params.instanceName}}</h1>
|
||||||
|
|
||||||
{{#if instanceUserAccount}}
|
{{#if verifyCredentials}}
|
||||||
<h2>Logged in as:</h2>
|
<h2>Logged in as:</h2>
|
||||||
<div class="acct-current-user">
|
<div class="acct-current-user">
|
||||||
<img alt="Profile picture for {{'@' + instanceUserAccount.acct}}"
|
<img alt="Profile picture for {{'@' + verifyCredentials.acct}}"
|
||||||
class="acct-avatar" src="{{instanceUserAccount.avatar}}" />
|
class="acct-avatar" src="{{verifyCredentials.avatar}}" />
|
||||||
<a class="acct-handle" rel="noopener" target="_blank"
|
<a class="acct-handle" rel="noopener" target="_blank"
|
||||||
href="{{instanceUserAccount.url}}">{{'@' + instanceUserAccount.acct}}</a>
|
href="{{verifyCredentials.url}}">{{'@' + verifyCredentials.acct}}</a>
|
||||||
<span class="acct-display-name">{{instanceUserAccount.display_name}}</span>
|
<span class="acct-display-name">{{verifyCredentials.display_name}}</span>
|
||||||
</div>
|
</div>
|
||||||
<h2>Theme:</h2>
|
<h2>Theme:</h2>
|
||||||
<form class="theme-chooser" aria-label="Choose a theme">
|
<form class="theme-chooser" aria-label="Choose a theme">
|
||||||
|
@ -97,7 +97,8 @@
|
||||||
import { store } from '../../_utils/store'
|
import { store } from '../../_utils/store'
|
||||||
import Layout from '../../_components/Layout.html'
|
import Layout from '../../_components/Layout.html'
|
||||||
import SettingsLayout from '../_components/SettingsLayout.html'
|
import SettingsLayout from '../_components/SettingsLayout.html'
|
||||||
import { getThisUserAccount } from '../../_utils/mastodon/user'
|
import { getVerifyCredentials } from '../../_utils/mastodon/user'
|
||||||
|
import { database } from '../../_utils/database/database'
|
||||||
import { themes } from '../../_static/themes'
|
import { themes } from '../../_static/themes'
|
||||||
import { switchToTheme } from '../../_utils/themeEngine'
|
import { switchToTheme } from '../../_utils/themeEngine'
|
||||||
import { goto } from 'sapper/runtime.js'
|
import { goto } from 'sapper/runtime.js'
|
||||||
|
@ -117,9 +118,18 @@
|
||||||
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 instanceData = loggedInInstances[instanceName]
|
let instanceData = loggedInInstances[instanceName]
|
||||||
let instanceUserAccount = await getThisUserAccount(instanceName, instanceData.access_token)
|
let verifyCredentials = await database.getInstanceVerifyCredentials(instanceName)
|
||||||
|
if (verifyCredentials) { // update
|
||||||
|
getVerifyCredentials(instanceName, instanceData.access_token).then(verifyCredentials => {
|
||||||
|
database.setInstanceVerifyCredentials(instanceName, verifyCredentials)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
verifyCredentials = await getVerifyCredentials(instanceName, instanceData.access_token)
|
||||||
|
database.setInstanceVerifyCredentials(instanceName, verifyCredentials)
|
||||||
|
verifyCredentials = verifyCredentials
|
||||||
|
}
|
||||||
this.set({
|
this.set({
|
||||||
instanceUserAccount: instanceUserAccount,
|
verifyCredentials: verifyCredentials,
|
||||||
selectedTheme: instanceThemes[instanceName]
|
selectedTheme: instanceThemes[instanceName]
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
@ -72,11 +72,12 @@
|
||||||
import Layout from '../../_components/Layout.html';
|
import Layout from '../../_components/Layout.html';
|
||||||
import SettingsLayout from '../_components/SettingsLayout.html'
|
import SettingsLayout from '../_components/SettingsLayout.html'
|
||||||
import { registerApplication, generateAuthLink, getAccessTokenFromAuthCode } from '../../_utils/mastodon/oauth'
|
import { registerApplication, generateAuthLink, getAccessTokenFromAuthCode } from '../../_utils/mastodon/oauth'
|
||||||
import { getThisUserAccount } from '../../_utils/mastodon/user'
|
import { getVerifyCredentials } from '../../_utils/mastodon/user'
|
||||||
import { store } from '../../_utils/store'
|
import { store } from '../../_utils/store'
|
||||||
import { goto } from 'sapper/runtime.js'
|
import { goto } from 'sapper/runtime.js'
|
||||||
import { switchToTheme } from '../../_utils/themeEngine'
|
import { switchToTheme } from '../../_utils/themeEngine'
|
||||||
import LoadingMask from '../../_components/LoadingMask'
|
import LoadingMask from '../../_components/LoadingMask'
|
||||||
|
import { database } from '../../_utils/database/database'
|
||||||
|
|
||||||
const REDIRECT_URI = (typeof location !== 'undefined' ?
|
const REDIRECT_URI = (typeof location !== 'undefined' ?
|
||||||
location.origin : 'https://pinafore.social') + '/settings/instances/add'
|
location.origin : 'https://pinafore.social') + '/settings/instances/add'
|
||||||
|
@ -177,7 +178,9 @@
|
||||||
this.store.save()
|
this.store.save()
|
||||||
switchToTheme('default')
|
switchToTheme('default')
|
||||||
// fire off request for account so it's cached in the SW
|
// fire off request for account so it's cached in the SW
|
||||||
getThisUserAccount(currentRegisteredInstanceName, instanceData.access_token)
|
getVerifyCredentials(currentRegisteredInstanceName, instanceData.access_token).then(verifyCredentials => {
|
||||||
|
database.setInstanceVerifyCredentials(currentRegisteredInstanceName, verifyCredentials)
|
||||||
|
})
|
||||||
goto('/')
|
goto('/')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,6 @@ const NETWORK_ONLY = [
|
||||||
]
|
]
|
||||||
|
|
||||||
const CACHE_FIRST = [
|
const CACHE_FIRST = [
|
||||||
'/api/v1/accounts/verify_credentials',
|
|
||||||
'/system/accounts/avatars'
|
'/system/accounts/avatars'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue