Refactor database for better code-splitting (#144)

This commit is contained in:
Nolan Lawson 2018-04-16 20:56:21 -07:00 committed by GitHub
parent 8fb00a961c
commit b231466fff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 588 additions and 525 deletions

View file

@ -1,11 +1,16 @@
import { getAccount, getRelationship } from '../_api/user' import { getAccount, getRelationship } from '../_api/user'
import { database } from '../_database/database' import {
getAccount as getAccountFromDatabase,
setAccount as setAccountInDatabase,
getRelationship as getRelationshipFromDatabase,
setRelationship as setRelationshipInDatabase
} from '../_database/accountsAndRelationships'
import { store } from '../_store/store' import { store } from '../_store/store'
async function updateAccount (accountId, instanceName, accessToken) { async function updateAccount (accountId, instanceName, accessToken) {
let localPromise = database.getAccount(instanceName, accountId) let localPromise = getAccountFromDatabase(instanceName, accountId)
let remotePromise = getAccount(instanceName, accessToken, accountId).then(account => { let remotePromise = getAccount(instanceName, accessToken, accountId).then(account => {
database.setAccount(instanceName, account) /* no await */ setAccountInDatabase(instanceName, account)
return account return account
}) })
@ -22,9 +27,9 @@ async function updateAccount (accountId, instanceName, accessToken) {
} }
async function updateRelationship (accountId, instanceName, accessToken) { async function updateRelationship (accountId, instanceName, accessToken) {
let localPromise = database.getRelationship(instanceName, accountId) let localPromise = getRelationshipFromDatabase(instanceName, accountId)
let remotePromise = getRelationship(instanceName, accessToken, accountId).then(relationship => { let remotePromise = getRelationship(instanceName, accessToken, accountId).then(relationship => {
database.setRelationship(instanceName, relationship) /* no await */ setRelationshipInDatabase(instanceName, relationship)
return relationship return relationship
}) })
try { try {

View file

@ -2,10 +2,10 @@ import { getAccessTokenFromAuthCode, registerApplication, generateAuthLink } fro
import { getInstanceInfo } from '../_api/instance' import { getInstanceInfo } from '../_api/instance'
import { goto } from 'sapper/runtime.js' import { goto } from 'sapper/runtime.js'
import { switchToTheme } from '../_utils/themeEngine' import { switchToTheme } from '../_utils/themeEngine'
import { database } from '../_database/database'
import { store } from '../_store/store' import { store } from '../_store/store'
import { updateVerifyCredentialsForInstance } from './instances' import { updateVerifyCredentialsForInstance } from './instances'
import { updateCustomEmojiForInstance } from './emoji' import { updateCustomEmojiForInstance } from './emoji'
import { setInstanceInfo as setInstanceInfoInDatabase } from '../_database/meta'
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'
@ -20,7 +20,7 @@ async function redirectToOauth () {
} }
let registrationPromise = registerApplication(instanceName, REDIRECT_URI) let registrationPromise = registerApplication(instanceName, REDIRECT_URI)
let instanceInfo = await getInstanceInfo(instanceName) let instanceInfo = await getInstanceInfo(instanceName)
await database.setInstanceInfo(instanceName, instanceInfo) // cache for later await setInstanceInfoInDatabase(instanceName, instanceInfo) // cache for later
let instanceData = await registrationPromise let instanceData = await registrationPromise
store.set({ store.set({
currentRegisteredInstanceName: instanceName, currentRegisteredInstanceName: instanceName,

View file

@ -1,5 +1,4 @@
import throttle from 'lodash-es/throttle' import throttle from 'lodash-es/throttle'
import { database } from '../_database/database'
import { mark, stop } from '../_utils/marks' import { mark, stop } from '../_utils/marks'
import { store } from '../_store/store' import { store } from '../_store/store'
import { scheduleIdleTask } from '../_utils/scheduleIdleTask' import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
@ -7,6 +6,9 @@ import uniqBy from 'lodash-es/uniqBy'
import uniq from 'lodash-es/uniq' import uniq from 'lodash-es/uniq'
import isEqual from 'lodash-es/isEqual' import isEqual from 'lodash-es/isEqual'
import { isMobile } from '../_utils/isMobile' import { isMobile } from '../_utils/isMobile'
import {
insertTimelineItems as insertTimelineItemsInDatabase
} from '../_database/timelines/insertion'
const STREAMING_THROTTLE_DELAY = 3000 const STREAMING_THROTTLE_DELAY = 3000
@ -28,7 +30,7 @@ async function insertUpdatesIntoTimeline (instanceName, timelineName, updates) {
return return
} }
await database.insertTimelineItems(instanceName, timelineName, updates) await insertTimelineItemsInDatabase(instanceName, timelineName, updates)
let itemIdsToAdd = store.getForTimeline(instanceName, timelineName, 'itemIdsToAdd') || [] let itemIdsToAdd = store.getForTimeline(instanceName, timelineName, 'itemIdsToAdd') || []
let newItemIdsToAdd = uniq([].concat(itemIdsToAdd).concat(updates.map(_ => _.id))) let newItemIdsToAdd = uniq([].concat(itemIdsToAdd).concat(updates.map(_ => _.id)))

View file

@ -2,13 +2,13 @@ import { store } from '../_store/store'
import { toast } from '../_utils/toast' import { toast } from '../_utils/toast'
import { postStatus as postStatusToServer } from '../_api/statuses' import { postStatus as postStatusToServer } from '../_api/statuses'
import { addStatusOrNotification } from './addStatusOrNotification' import { addStatusOrNotification } from './addStatusOrNotification'
import { database } from '../_database/database' import { getStatus as getStatusFromDatabase } from '../_database/timelines/getStatusOrNotification'
import { emit } from '../_utils/eventBus' import { emit } from '../_utils/eventBus'
import { putMediaDescription } from '../_api/media' import { putMediaDescription } from '../_api/media'
export async function insertHandleForReply (statusId) { export async function insertHandleForReply (statusId) {
let instanceName = store.get('currentInstance') let instanceName = store.get('currentInstance')
let status = await database.getStatus(instanceName, statusId) let status = await getStatusFromDatabase(instanceName, statusId)
let verifyCredentials = store.get('currentVerifyCredentials') let verifyCredentials = store.get('currentVerifyCredentials')
let originalStatus = status.reblog || status let originalStatus = status.reblog || status
let accounts = [originalStatus.account].concat(originalStatus.mentions || []) let accounts = [originalStatus.account].concat(originalStatus.mentions || [])

View file

@ -1,9 +1,11 @@
import { getIdsThatRebloggedThisStatus, getNotificationIdsForStatuses } from './statuses' import { getIdsThatRebloggedThisStatus, getNotificationIdsForStatuses } from './statuses'
import { store } from '../_store/store' import { store } from '../_store/store'
import { scheduleIdleTask } from '../_utils/scheduleIdleTask' import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
import { database } from '../_database/database'
import forEach from 'lodash-es/forEach' import forEach from 'lodash-es/forEach'
import isEqual from 'lodash-es/isEqual' import isEqual from 'lodash-es/isEqual'
import {
deleteStatusesAndNotifications as deleteStatusesAndNotificationsFromDatabase
} from '../_database/timelines/deletion'
function filterItemIdsFromTimelines (instanceName, timelineFilter, idFilter) { function filterItemIdsFromTimelines (instanceName, timelineFilter, idFilter) {
let keys = ['timelineItemIds', 'itemIdsToAdd'] let keys = ['timelineItemIds', 'itemIdsToAdd']
@ -43,7 +45,7 @@ function deleteNotificationIdsFromStore (instanceName, idsToDelete) {
async function deleteStatusesAndNotifications (instanceName, statusIdsToDelete, notificationIdsToDelete) { async function deleteStatusesAndNotifications (instanceName, statusIdsToDelete, notificationIdsToDelete) {
deleteStatusIdsFromStore(instanceName, statusIdsToDelete) deleteStatusIdsFromStore(instanceName, statusIdsToDelete)
deleteNotificationIdsFromStore(instanceName, notificationIdsToDelete) deleteNotificationIdsFromStore(instanceName, notificationIdsToDelete)
await database.deleteStatusesAndNotifications(instanceName, statusIdsToDelete, notificationIdsToDelete) await deleteStatusesAndNotificationsFromDatabase(instanceName, statusIdsToDelete, notificationIdsToDelete)
} }
async function doDeleteStatus (instanceName, statusId) { async function doDeleteStatus (instanceName, statusId) {

View file

@ -1,5 +1,8 @@
import { cacheFirstUpdateAfter } from '../_utils/sync' import { cacheFirstUpdateAfter } from '../_utils/sync'
import { database } from '../_database/database' import {
getCustomEmoji as getCustomEmojiFromDatabase,
setCustomEmoji as setCustomEmojiInDatabase
} from '../_database/meta'
import { getCustomEmoji } from '../_api/emoji' import { getCustomEmoji } from '../_api/emoji'
import { store } from '../_store/store' import { store } from '../_store/store'
import { substring } from 'stringz' import { substring } from 'stringz'
@ -7,8 +10,8 @@ import { substring } from 'stringz'
export async function updateCustomEmojiForInstance (instanceName) { export async function updateCustomEmojiForInstance (instanceName) {
await cacheFirstUpdateAfter( await cacheFirstUpdateAfter(
() => getCustomEmoji(instanceName), () => getCustomEmoji(instanceName),
() => database.getCustomEmoji(instanceName), () => getCustomEmojiFromDatabase(instanceName),
emoji => database.setCustomEmoji(instanceName, emoji), emoji => setCustomEmojiInDatabase(instanceName, emoji),
emoji => { emoji => {
let customEmoji = store.get('customEmoji') let customEmoji = store.get('customEmoji')
customEmoji[instanceName] = emoji customEmoji[instanceName] = emoji

View file

@ -1,7 +1,9 @@
import { favoriteStatus, unfavoriteStatus } from '../_api/favorite' import { favoriteStatus, unfavoriteStatus } from '../_api/favorite'
import { store } from '../_store/store' import { store } from '../_store/store'
import { database } from '../_database/database'
import { toast } from '../_utils/toast' import { toast } from '../_utils/toast'
import {
setStatusFavorited as setStatusFavoritedInDatabase
} from '../_database/timelines/updateStatus'
export async function setFavorited (statusId, favorited) { export async function setFavorited (statusId, favorited) {
if (!store.get('online')) { if (!store.get('online')) {
@ -16,7 +18,7 @@ export async function setFavorited (statusId, favorited) {
store.setStatusFavorited(instanceName, statusId, favorited) // optimistic update store.setStatusFavorited(instanceName, statusId, favorited) // optimistic update
try { try {
await networkPromise await networkPromise
await database.setStatusFavorited(instanceName, statusId, favorited) await setStatusFavoritedInDatabase(instanceName, statusId, favorited)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
toast.say(`Failed to ${favorited ? 'favorite' : 'unfavorite'}. ` + (e.message || '')) toast.say(`Failed to ${favorited ? 'favorite' : 'unfavorite'}. ` + (e.message || ''))

View file

@ -1,8 +1,10 @@
import { store } from '../_store/store' import { store } from '../_store/store'
import { followAccount, unfollowAccount } from '../_api/follow' import { followAccount, unfollowAccount } from '../_api/follow'
import { database } from '../_database/database'
import { toast } from '../_utils/toast' import { toast } from '../_utils/toast'
import { updateProfileAndRelationship } from './accounts' import { updateProfileAndRelationship } from './accounts'
import {
getRelationship as getRelationshipFromDatabase
} from '../_database/accountsAndRelationships'
export async function setAccountFollowed (accountId, follow, toastOnSuccess) { export async function setAccountFollowed (accountId, follow, toastOnSuccess) {
let instanceName = store.get('currentInstance') let instanceName = store.get('currentInstance')
@ -15,7 +17,7 @@ export async function setAccountFollowed (accountId, follow, toastOnSuccess) {
account = await unfollowAccount(instanceName, accessToken, accountId) account = await unfollowAccount(instanceName, accessToken, accountId)
} }
await updateProfileAndRelationship(accountId) await updateProfileAndRelationship(accountId)
let relationship = await database.getRelationship(instanceName, accountId) let relationship = await getRelationshipFromDatabase(instanceName, accountId)
if (toastOnSuccess) { if (toastOnSuccess) {
if (follow) { if (follow) {
if (account.locked && relationship.requested) { if (account.locked && relationship.requested) {

View file

@ -2,10 +2,16 @@ import { getVerifyCredentials } from '../_api/user'
import { store } from '../_store/store' import { store } from '../_store/store'
import { switchToTheme } from '../_utils/themeEngine' import { switchToTheme } from '../_utils/themeEngine'
import { toast } from '../_utils/toast' import { toast } from '../_utils/toast'
import { database } from '../_database/database'
import { goto } from 'sapper/runtime.js' import { goto } from 'sapper/runtime.js'
import { cacheFirstUpdateAfter } from '../_utils/sync' import { cacheFirstUpdateAfter } from '../_utils/sync'
import { getInstanceInfo } from '../_api/instance' import { getInstanceInfo } from '../_api/instance'
import { clearDatabaseForInstance } from '../_database/clear'
import {
getInstanceVerifyCredentials as getInstanceVerifyCredentialsFromDatabase,
setInstanceVerifyCredentials as setInstanceVerifyCredentialsInDatabase,
getInstanceInfo as getInstanceInfoFromDatabase,
setInstanceInfo as setInstanceInfoInDatabase
} from '../_database/meta'
export function changeTheme (instanceName, newTheme) { export function changeTheme (instanceName, newTheme) {
let instanceThemes = store.get('instanceThemes') let instanceThemes = store.get('instanceThemes')
@ -53,7 +59,7 @@ export async function logOutOfInstance (instanceName) {
store.save() store.save()
toast.say(`Logged out of ${instanceName}`) toast.say(`Logged out of ${instanceName}`)
switchToTheme(instanceThemes[newInstance] || 'default') switchToTheme(instanceThemes[newInstance] || 'default')
await database.clearDatabaseForInstance(instanceName) await clearDatabaseForInstance(instanceName)
goto('/settings/instances') goto('/settings/instances')
} }
@ -68,8 +74,8 @@ export async function updateVerifyCredentialsForInstance (instanceName) {
let accessToken = loggedInInstances[instanceName].access_token let accessToken = loggedInInstances[instanceName].access_token
await cacheFirstUpdateAfter( await cacheFirstUpdateAfter(
() => getVerifyCredentials(instanceName, accessToken), () => getVerifyCredentials(instanceName, accessToken),
() => database.getInstanceVerifyCredentials(instanceName), () => getInstanceVerifyCredentialsFromDatabase(instanceName),
verifyCredentials => database.setInstanceVerifyCredentials(instanceName, verifyCredentials), verifyCredentials => setInstanceVerifyCredentialsInDatabase(instanceName, verifyCredentials),
verifyCredentials => setStoreVerifyCredentials(instanceName, verifyCredentials) verifyCredentials => setStoreVerifyCredentials(instanceName, verifyCredentials)
) )
} }
@ -81,8 +87,8 @@ export async function updateVerifyCredentialsForCurrentInstance () {
export async function updateInstanceInfo (instanceName) { export async function updateInstanceInfo (instanceName) {
await cacheFirstUpdateAfter( await cacheFirstUpdateAfter(
() => getInstanceInfo(instanceName), () => getInstanceInfo(instanceName),
() => database.getInstanceInfo(instanceName), () => getInstanceInfoFromDatabase(instanceName),
info => database.setInstanceInfo(instanceName, info), info => setInstanceInfoInDatabase(instanceName, info),
info => { info => {
let instanceInfos = store.get('instanceInfos') let instanceInfos = store.get('instanceInfos')
instanceInfos[instanceName] = info instanceInfos[instanceName] = info

View file

@ -1,7 +1,10 @@
import { store } from '../_store/store' import { store } from '../_store/store'
import { database } from '../_database/database'
import { getLists } from '../_api/lists' import { getLists } from '../_api/lists'
import { cacheFirstUpdateAfter } from '../_utils/sync' import { cacheFirstUpdateAfter } from '../_utils/sync'
import {
getLists as getListsFromDatabase,
setLists as setListsInDatabase
} from '../_database/meta'
export async function updateLists () { export async function updateLists () {
let instanceName = store.get('currentInstance') let instanceName = store.get('currentInstance')
@ -9,8 +12,8 @@ export async function updateLists () {
await cacheFirstUpdateAfter( await cacheFirstUpdateAfter(
() => getLists(instanceName, accessToken), () => getLists(instanceName, accessToken),
() => database.getLists(instanceName), () => getListsFromDatabase(instanceName),
lists => database.setLists(instanceName, lists), lists => setListsInDatabase(instanceName, lists),
lists => { lists => {
let instanceLists = store.get('instanceLists') let instanceLists = store.get('instanceLists')
instanceLists[instanceName] = lists instanceLists[instanceName] = lists

View file

@ -1,7 +1,12 @@
import { store } from '../_store/store' import { store } from '../_store/store'
import { cacheFirstUpdateAfter } from '../_utils/sync' import { cacheFirstUpdateAfter } from '../_utils/sync'
import { getPinnedStatuses } from '../_api/pinnedStatuses' import {
import { database } from '../_database/database' getPinnedStatuses as getPinnedStatusesFromDatabase,
insertPinnedStatuses as insertPinnedStatusesInDatabase
} from '../_database/timelines/pinnedStatuses'
import {
getPinnedStatuses
} from '../_api/pinnedStatuses'
export async function updatePinnedStatusesForAccount (accountId) { export async function updatePinnedStatusesForAccount (accountId) {
let instanceName = store.get('currentInstance') let instanceName = store.get('currentInstance')
@ -9,8 +14,8 @@ export async function updatePinnedStatusesForAccount (accountId) {
await cacheFirstUpdateAfter( await cacheFirstUpdateAfter(
() => getPinnedStatuses(instanceName, accessToken, accountId), () => getPinnedStatuses(instanceName, accessToken, accountId),
() => database.getPinnedStatuses(instanceName, accountId), () => getPinnedStatusesFromDatabase(instanceName, accountId),
statuses => database.insertPinnedStatuses(instanceName, accountId, statuses), statuses => insertPinnedStatusesInDatabase(instanceName, accountId, statuses),
statuses => { statuses => {
let $pinnedStatuses = store.get('pinnedStatuses') let $pinnedStatuses = store.get('pinnedStatuses')
$pinnedStatuses[instanceName] = $pinnedStatuses[instanceName] || {} $pinnedStatuses[instanceName] = $pinnedStatuses[instanceName] || {}

View file

@ -1,7 +1,7 @@
import { store } from '../_store/store' import { store } from '../_store/store'
import { database } from '../_database/database'
import { toast } from '../_utils/toast' import { toast } from '../_utils/toast'
import { reblogStatus, unreblogStatus } from '../_api/reblog' import { reblogStatus, unreblogStatus } from '../_api/reblog'
import { setStatusReblogged as setStatusRebloggedInDatabase } from '../_database/timelines/updateStatus'
export async function setReblogged (statusId, reblogged) { export async function setReblogged (statusId, reblogged) {
if (!store.get('online')) { if (!store.get('online')) {
@ -16,7 +16,7 @@ export async function setReblogged (statusId, reblogged) {
store.setStatusReblogged(instanceName, statusId, reblogged) // optimistic update store.setStatusReblogged(instanceName, statusId, reblogged) // optimistic update
try { try {
await networkPromise await networkPromise
await database.setStatusReblogged(instanceName, statusId, reblogged) await setStatusRebloggedInDatabase(instanceName, statusId, reblogged)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
toast.say(`Failed to ${reblogged ? 'boost' : 'unboost'}. ` + (e.message || '')) toast.say(`Failed to ${reblogged ? 'boost' : 'unboost'}. ` + (e.message || ''))

View file

@ -1,7 +1,13 @@
import { database } from '../_database/database' import {
getNotificationIdsForStatuses as getNotificationIdsForStatusesFromDatabase,
getReblogsForStatus as getReblogsForStatusFromDatabase
} from '../_database/timelines/lookup'
import {
getStatus as getStatusFromDatabase
} from '../_database/timelines/getStatusOrNotification'
export async function getIdThatThisStatusReblogged (instanceName, statusId) { export async function getIdThatThisStatusReblogged (instanceName, statusId) {
let status = await database.getStatus(instanceName, statusId) let status = await getStatusFromDatabase(instanceName, statusId)
return status.reblog && status.reblog.id return status.reblog && status.reblog.id
} }
@ -13,9 +19,9 @@ export async function getIdsThatTheseStatusesReblogged (instanceName, statusIds)
} }
export async function getIdsThatRebloggedThisStatus (instanceName, statusId) { export async function getIdsThatRebloggedThisStatus (instanceName, statusId) {
return database.getReblogsForStatus(instanceName, statusId) return getReblogsForStatusFromDatabase(instanceName, statusId)
} }
export async function getNotificationIdsForStatuses (instanceName, statusIds) { export async function getNotificationIdsForStatuses (instanceName, statusIds) {
return database.getNotificationIdsForStatuses(instanceName, statusIds) return getNotificationIdsForStatusesFromDatabase(instanceName, statusIds)
} }

View file

@ -1,11 +1,16 @@
import { store } from '../_store/store' import { store } from '../_store/store'
import { database } from '../_database/database'
import { getTimeline } from '../_api/timelines' import { getTimeline } from '../_api/timelines'
import { toast } from '../_utils/toast' import { toast } from '../_utils/toast'
import { mark, stop } from '../_utils/marks' import { mark, stop } from '../_utils/marks'
import { mergeArrays } from '../_utils/arrays' import { mergeArrays } from '../_utils/arrays'
import { byItemIds } from '../_utils/sorting' import { byItemIds } from '../_utils/sorting'
import isEqual from 'lodash-es/isEqual' import isEqual from 'lodash-es/isEqual'
import {
insertTimelineItems as insertTimelineItemsInDatabase
} from '../_database/timelines/insertion'
import {
getTimeline as getTimelineFromDatabase
} from '../_database/timelines/pagination'
const FETCH_LIMIT = 20 const FETCH_LIMIT = 20
@ -14,16 +19,16 @@ async function fetchTimelineItems (instanceName, accessToken, timelineName, last
let items let items
let stale = false let stale = false
if (!online) { if (!online) {
items = await database.getTimeline(instanceName, timelineName, lastTimelineItemId, FETCH_LIMIT) items = await getTimelineFromDatabase(instanceName, timelineName, lastTimelineItemId, FETCH_LIMIT)
stale = true stale = true
} else { } else {
try { try {
items = await getTimeline(instanceName, accessToken, timelineName, lastTimelineItemId, FETCH_LIMIT) items = await getTimeline(instanceName, accessToken, timelineName, lastTimelineItemId, FETCH_LIMIT)
/* no await */ database.insertTimelineItems(instanceName, timelineName, items) /* no await */ insertTimelineItemsInDatabase(instanceName, timelineName, items)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
toast.say('Internet request failed. Showing offline content.') toast.say('Internet request failed. Showing offline content.')
items = await database.getTimeline(instanceName, timelineName, lastTimelineItemId, FETCH_LIMIT) items = await getTimelineFromDatabase(instanceName, timelineName, lastTimelineItemId, FETCH_LIMIT)
stale = true stale = true
} }
} }

View file

@ -39,12 +39,14 @@
</style> </style>
<script> <script>
import { store } from '../../_store/store' import { store } from '../../_store/store'
import { database } from '../../_database/database'
import { insertUsername } from '../../_actions/compose' import { insertUsername } from '../../_actions/compose'
import { insertEmojiAtPosition } from '../../_actions/emoji' import { insertEmojiAtPosition } from '../../_actions/emoji'
import { scheduleIdleTask } from '../../_utils/scheduleIdleTask' import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
import { once } from '../../_utils/once' import { once } from '../../_utils/once'
import ComposeAutosuggestionList from './ComposeAutosuggestionList.html' import ComposeAutosuggestionList from './ComposeAutosuggestionList.html'
import {
searchAccountsByUsername as searchAccountsByUsernameInDatabase
} from '../../_database/accountsAndRelationships'
const SEARCH_RESULTS_LIMIT = 4 const SEARCH_RESULTS_LIMIT = 4
const DATABASE_SEARCH_RESULTS_LIMIT = 30 const DATABASE_SEARCH_RESULTS_LIMIT = 30
@ -116,7 +118,7 @@
async searchAccounts(searchText) { async searchAccounts(searchText) {
searchText = searchText.substring(1) searchText = searchText.substring(1)
let currentInstance = this.store.get('currentInstance') let currentInstance = this.store.get('currentInstance')
let results = await database.searchAccountsByUsername( let results = await searchAccountsByUsernameInDatabase(
currentInstance, searchText, DATABASE_SEARCH_RESULTS_LIMIT) currentInstance, searchText, DATABASE_SEARCH_RESULTS_LIMIT)
return results.slice(0, SEARCH_RESULTS_LIMIT) return results.slice(0, SEARCH_RESULTS_LIMIT)
}, },

View file

@ -45,7 +45,10 @@
import VirtualList from '../virtualList/VirtualList.html' import VirtualList from '../virtualList/VirtualList.html'
import PseudoVirtualList from '../pseudoVirtualList/PseudoVirtualList.html' import PseudoVirtualList from '../pseudoVirtualList/PseudoVirtualList.html'
import { timelines } from '../../_static/timelines' import { timelines } from '../../_static/timelines'
import { database } from '../../_database/database' import {
getStatus as getStatusFromDatabase,
getNotification as getNotificationFromDatabase
} from '../../_database/timelines/getStatusOrNotification'
import { import {
fetchTimelineItemsOnScrollToBottom, fetchTimelineItemsOnScrollToBottom,
setupTimeline, setupTimeline,
@ -87,9 +90,9 @@
timelineValue timelineValue
} }
if (timelineType === 'notifications') { if (timelineType === 'notifications') {
res.notification = await database.getNotification($currentInstance, itemId) res.notification = await getNotificationFromDatabase($currentInstance, itemId)
} else { } else {
res.status = await database.getStatus($currentInstance, itemId) res.status = await getStatusFromDatabase($currentInstance, itemId)
} }
return res return res
}, },

View file

@ -1,3 +0,0 @@
import * as database from './databaseCore'
export { database }

View file

@ -1,4 +0,0 @@
export * from './accountsAndRelationships'
export * from './clear'
export * from './meta'
export * from './timelines'

View file

@ -1,470 +0,0 @@
import difference from 'lodash-es/difference'
import times from 'lodash-es/times'
import { cloneForStorage } from './helpers'
import { dbPromise, getDatabase } from './databaseLifecycle'
import {
accountsCache, deleteFromCache, getInCache, hasInCache, notificationsCache, setInCache,
statusesCache
} from './cache'
import { scheduleCleanup } from './cleanup'
import {
ACCOUNTS_STORE,
NOTIFICATION_TIMELINES_STORE,
NOTIFICATIONS_STORE, PINNED_STATUSES_STORE,
STATUS_TIMELINES_STORE,
STATUSES_STORE,
ACCOUNT_ID,
REBLOG_ID,
STATUS_ID, THREADS_STORE
} from './constants'
import {
createThreadKeyRange,
createTimelineKeyRange,
createTimelineId,
createThreadId,
createPinnedStatusKeyRange,
createPinnedStatusId
} from './keys'
import { deleteAll } from './utils'
function cacheStatus (status, instanceName) {
setInCache(statusesCache, instanceName, status.id, status)
setInCache(accountsCache, instanceName, status.account.id, status.account)
if (status.reblog) {
setInCache(accountsCache, instanceName, status.reblog.account.id, status.reblog.account)
}
}
//
// pagination
//
async function getNotificationTimeline (instanceName, timeline, maxId, limit) {
let storeNames = [NOTIFICATION_TIMELINES_STORE, NOTIFICATIONS_STORE, STATUSES_STORE, ACCOUNTS_STORE]
const db = await getDatabase(instanceName)
return dbPromise(db, storeNames, 'readonly', (stores, callback) => {
let [ timelineStore, notificationsStore, statusesStore, accountsStore ] = stores
let keyRange = createTimelineKeyRange(timeline, maxId)
timelineStore.getAll(keyRange, limit).onsuccess = e => {
let timelineResults = e.target.result
let res = new Array(timelineResults.length)
timelineResults.forEach((notificationId, i) => {
fetchNotification(notificationsStore, statusesStore, accountsStore, notificationId, notification => {
res[i] = notification
})
})
callback(res)
}
})
}
async function getStatusTimeline (instanceName, timeline, maxId, limit) {
let storeNames = [STATUS_TIMELINES_STORE, STATUSES_STORE, ACCOUNTS_STORE]
const db = await getDatabase(instanceName)
return dbPromise(db, storeNames, 'readonly', (stores, callback) => {
let [ timelineStore, statusesStore, accountsStore ] = stores
let getReq = timelineStore.getAll(createTimelineKeyRange(timeline, maxId), limit)
getReq.onsuccess = e => {
let timelineResults = e.target.result
let res = new Array(timelineResults.length)
timelineResults.forEach((statusId, i) => {
fetchStatus(statusesStore, accountsStore, statusId, status => {
res[i] = status
})
})
callback(res)
}
})
}
async function getStatusThread (instanceName, statusId) {
let storeNames = [THREADS_STORE, STATUSES_STORE, ACCOUNTS_STORE]
const db = await getDatabase(instanceName)
return dbPromise(db, storeNames, 'readonly', (stores, callback) => {
let [ threadsStore, statusesStore, accountsStore ] = stores
let keyRange = createThreadKeyRange(statusId)
threadsStore.getAll(keyRange).onsuccess = e => {
let thread = e.target.result
if (thread.length) {
let res = new Array(thread.length)
callback(res)
thread.forEach((otherStatusId, i) => {
fetchStatus(statusesStore, accountsStore, otherStatusId, status => {
res[i] = status
})
})
} else {
// thread not cached; just make a "fake" thread with only one status in it
fetchStatus(statusesStore, accountsStore, statusId, status => {
let res = [status]
callback(res)
})
}
}
})
}
export async function getTimeline (instanceName, timeline, maxId = null, limit = 20) {
if (timeline === 'notifications') {
return getNotificationTimeline(instanceName, timeline, maxId, limit)
} else if (timeline.startsWith('status/')) {
let statusId = timeline.split('/').slice(-1)[0]
return getStatusThread(instanceName, statusId)
} else {
return getStatusTimeline(instanceName, timeline, maxId, limit)
}
}
//
// insertion
//
function putStatus (statusesStore, status) {
statusesStore.put(cloneForStorage(status))
}
function putAccount (accountsStore, account) {
accountsStore.put(cloneForStorage(account))
}
function putNotification (notificationsStore, notification) {
notificationsStore.put(cloneForStorage(notification))
}
function storeAccount (accountsStore, account) {
putAccount(accountsStore, account)
}
function storeStatus (statusesStore, accountsStore, status) {
putStatus(statusesStore, status)
putAccount(accountsStore, status.account)
if (status.reblog) {
putStatus(statusesStore, status.reblog)
putAccount(accountsStore, status.reblog.account)
}
}
function storeNotification (notificationsStore, statusesStore, accountsStore, notification) {
if (notification.status) {
storeStatus(statusesStore, accountsStore, notification.status)
}
storeAccount(accountsStore, notification.account)
putNotification(notificationsStore, notification)
}
function fetchAccount (accountsStore, id, callback) {
accountsStore.get(id).onsuccess = e => {
callback(e.target.result)
}
}
function fetchStatus (statusesStore, accountsStore, id, callback) {
statusesStore.get(id).onsuccess = e => {
let status = e.target.result
callback(status)
fetchAccount(accountsStore, status[ACCOUNT_ID], account => {
status.account = account
})
if (status[REBLOG_ID]) {
fetchStatus(statusesStore, accountsStore, status[REBLOG_ID], reblog => {
status.reblog = reblog
})
}
}
}
function fetchNotification (notificationsStore, statusesStore, accountsStore, id, callback) {
notificationsStore.get(id).onsuccess = e => {
let notification = e.target.result
callback(notification)
fetchAccount(accountsStore, notification[ACCOUNT_ID], account => {
notification.account = account
})
if (notification[STATUS_ID]) {
fetchStatus(statusesStore, accountsStore, notification[STATUS_ID], status => {
notification.status = status
})
}
}
}
async function insertTimelineNotifications (instanceName, timeline, notifications) {
for (let notification of notifications) {
setInCache(notificationsCache, instanceName, notification.id, notification)
setInCache(accountsCache, instanceName, notification.account.id, notification.account)
if (notification.status) {
setInCache(statusesCache, instanceName, notification.status.id, notification.status)
}
}
const db = await getDatabase(instanceName)
let storeNames = [NOTIFICATION_TIMELINES_STORE, NOTIFICATIONS_STORE, ACCOUNTS_STORE, STATUSES_STORE]
await dbPromise(db, storeNames, 'readwrite', (stores) => {
let [ timelineStore, notificationsStore, accountsStore, statusesStore ] = stores
for (let notification of notifications) {
storeNotification(notificationsStore, statusesStore, accountsStore, notification)
timelineStore.put(notification.id, createTimelineId(timeline, notification.id))
}
})
}
async function insertTimelineStatuses (instanceName, timeline, statuses) {
for (let status of statuses) {
cacheStatus(status, instanceName)
}
const db = await getDatabase(instanceName)
let storeNames = [STATUS_TIMELINES_STORE, STATUSES_STORE, ACCOUNTS_STORE]
await dbPromise(db, storeNames, 'readwrite', (stores) => {
let [ timelineStore, statusesStore, accountsStore ] = stores
for (let status of statuses) {
storeStatus(statusesStore, accountsStore, status)
timelineStore.put(status.id, createTimelineId(timeline, status.id))
}
})
}
async function insertStatusThread (instanceName, statusId, statuses) {
for (let status of statuses) {
cacheStatus(status, instanceName)
}
const db = await getDatabase(instanceName)
let storeNames = [THREADS_STORE, STATUSES_STORE, ACCOUNTS_STORE]
await dbPromise(db, storeNames, 'readwrite', (stores) => {
let [ threadsStore, statusesStore, accountsStore ] = stores
threadsStore.getAllKeys(createThreadKeyRange(statusId)).onsuccess = e => {
let existingKeys = e.target.result
let newKeys = times(statuses.length, i => createThreadId(statusId, i))
let keysToDelete = difference(existingKeys, newKeys)
for (let key of keysToDelete) {
threadsStore.delete(key)
}
}
statuses.forEach((otherStatus, i) => {
storeStatus(statusesStore, accountsStore, otherStatus)
threadsStore.put(otherStatus.id, createThreadId(statusId, i))
})
})
}
export async function insertTimelineItems (instanceName, timeline, timelineItems) {
/* no await */ scheduleCleanup()
if (timeline === 'notifications') {
return insertTimelineNotifications(instanceName, timeline, timelineItems)
} else if (timeline.startsWith('status/')) {
let statusId = timeline.split('/').slice(-1)[0]
return insertStatusThread(instanceName, statusId, timelineItems)
} else {
return insertTimelineStatuses(instanceName, timeline, timelineItems)
}
}
//
// get
//
export async function getStatus (instanceName, id) {
if (hasInCache(statusesCache, instanceName, id)) {
return getInCache(statusesCache, instanceName, id)
}
const db = await getDatabase(instanceName)
let storeNames = [STATUSES_STORE, ACCOUNTS_STORE]
let result = await dbPromise(db, storeNames, 'readonly', (stores, callback) => {
let [ statusesStore, accountsStore ] = stores
fetchStatus(statusesStore, accountsStore, id, callback)
})
setInCache(statusesCache, instanceName, id, result)
return result
}
export async function getNotification (instanceName, id) {
if (hasInCache(notificationsCache, instanceName, id)) {
return getInCache(notificationsCache, instanceName, id)
}
const db = await getDatabase(instanceName)
let storeNames = [NOTIFICATIONS_STORE, STATUSES_STORE, ACCOUNTS_STORE]
let result = await dbPromise(db, storeNames, 'readonly', (stores, callback) => {
let [ notificationsStore, statusesStore, accountsStore ] = stores
fetchNotification(notificationsStore, statusesStore, accountsStore, id, callback)
})
setInCache(notificationsCache, instanceName, id, result)
return result
}
//
// lookup by reblogs
//
export async function getReblogsForStatus (instanceName, id) {
const db = await getDatabase(instanceName)
await dbPromise(db, STATUSES_STORE, 'readonly', (statusesStore, callback) => {
statusesStore.index(REBLOG_ID).getAll(IDBKeyRange.only(id)).onsuccess = e => {
callback(e.target.result)
}
})
}
//
// lookups by statusId
//
export async function getNotificationIdsForStatuses (instanceName, statusIds) {
const db = await getDatabase(instanceName)
return dbPromise(db, NOTIFICATIONS_STORE, 'readonly', (notificationsStore, callback) => {
let res = []
callback(res)
statusIds.forEach(statusId => {
let req = notificationsStore.index(STATUS_ID).getAllKeys(IDBKeyRange.only(statusId))
req.onsuccess = e => {
for (let id of e.target.result) {
res.push(id)
}
}
})
})
}
//
// deletes
//
export async function deleteStatusesAndNotifications (instanceName, statusIds, notificationIds) {
for (let statusId of statusIds) {
deleteFromCache(statusesCache, instanceName, statusId)
}
for (let notificationId of notificationIds) {
deleteFromCache(notificationsCache, instanceName, notificationId)
}
const db = await getDatabase(instanceName)
let storeNames = [
STATUSES_STORE,
STATUS_TIMELINES_STORE,
NOTIFICATIONS_STORE,
NOTIFICATION_TIMELINES_STORE,
PINNED_STATUSES_STORE,
THREADS_STORE
]
await dbPromise(db, storeNames, 'readwrite', (stores) => {
let [
statusesStore,
statusTimelinesStore,
notificationsStore,
notificationTimelinesStore,
pinnedStatusesStore,
threadsStore
] = stores
function deleteStatus (statusId) {
statusesStore.delete(statusId)
deleteAll(
pinnedStatusesStore,
pinnedStatusesStore.index('statusId'),
IDBKeyRange.only(statusId)
)
deleteAll(
statusTimelinesStore,
statusTimelinesStore.index('statusId'),
IDBKeyRange.only(statusId)
)
deleteAll(
threadsStore,
threadsStore.index('statusId'),
IDBKeyRange.only(statusId)
)
deleteAll(
threadsStore,
threadsStore,
createThreadKeyRange(statusId)
)
}
function deleteNotification (notificationId) {
notificationsStore.delete(notificationId)
deleteAll(
notificationTimelinesStore,
notificationTimelinesStore.index('notificationId'),
IDBKeyRange.only(notificationId)
)
}
for (let statusId of statusIds) {
deleteStatus(statusId)
}
for (let notificationId of notificationIds) {
deleteNotification(notificationId)
}
})
}
//
// pinned statuses
//
export async function insertPinnedStatuses (instanceName, accountId, statuses) {
for (let status of statuses) {
cacheStatus(status, instanceName)
}
const db = await getDatabase(instanceName)
let storeNames = [PINNED_STATUSES_STORE, STATUSES_STORE, ACCOUNTS_STORE]
await dbPromise(db, storeNames, 'readwrite', (stores) => {
let [ pinnedStatusesStore, statusesStore, accountsStore ] = stores
statuses.forEach((status, i) => {
storeStatus(statusesStore, accountsStore, status)
pinnedStatusesStore.put(status.id, createPinnedStatusId(accountId, i))
})
})
}
export async function getPinnedStatuses (instanceName, accountId) {
let storeNames = [PINNED_STATUSES_STORE, STATUSES_STORE, ACCOUNTS_STORE]
const db = await getDatabase(instanceName)
return dbPromise(db, storeNames, 'readonly', (stores, callback) => {
let [ pinnedStatusesStore, statusesStore, accountsStore ] = stores
let keyRange = createPinnedStatusKeyRange(accountId)
pinnedStatusesStore.getAll(keyRange).onsuccess = e => {
let pinnedResults = e.target.result
let res = new Array(pinnedResults.length)
pinnedResults.forEach((statusId, i) => {
fetchStatus(statusesStore, accountsStore, statusId, status => {
res[i] = status
})
})
callback(res)
}
})
}
//
// update statuses
//
async function updateStatus (instanceName, statusId, updateFunc) {
const db = await getDatabase(instanceName)
if (hasInCache(statusesCache, instanceName, statusId)) {
let status = getInCache(statusesCache, instanceName, statusId)
updateFunc(status)
cacheStatus(status, instanceName)
}
return dbPromise(db, STATUSES_STORE, 'readwrite', (statusesStore) => {
statusesStore.get(statusId).onsuccess = e => {
let status = e.target.result
updateFunc(status)
putStatus(statusesStore, status)
}
})
}
export async function setStatusFavorited (instanceName, statusId, favorited) {
return updateStatus(instanceName, statusId, status => {
let delta = (favorited ? 1 : 0) - (status.favourited ? 1 : 0)
status.favourited = favorited
status.favourites_count = (status.favourites_count || 0) + delta
})
}
export async function setStatusReblogged (instanceName, statusId, reblogged) {
return updateStatus(instanceName, statusId, status => {
let delta = (reblogged ? 1 : 0) - (status.reblogged ? 1 : 0)
status.reblogged = reblogged
status.reblogs_count = (status.reblogs_count || 0) + delta
})
}

View file

@ -0,0 +1,11 @@
import {
accountsCache, setInCache, statusesCache
} from '../cache'
export function cacheStatus (status, instanceName) {
setInCache(statusesCache, instanceName, status.id, status)
setInCache(accountsCache, instanceName, status.account.id, status.account)
if (status.reblog) {
setInCache(accountsCache, instanceName, status.reblog.account.id, status.reblog.account)
}
}

View file

@ -0,0 +1,84 @@
import { dbPromise, getDatabase } from '../databaseLifecycle'
import {
deleteFromCache, notificationsCache,
statusesCache
} from '../cache'
import {
NOTIFICATION_TIMELINES_STORE,
NOTIFICATIONS_STORE, PINNED_STATUSES_STORE,
STATUS_TIMELINES_STORE,
STATUSES_STORE,
THREADS_STORE
} from '../constants'
import {
createThreadKeyRange
} from '../keys'
import { deleteAll } from '../utils'
export async function deleteStatusesAndNotifications (instanceName, statusIds, notificationIds) {
for (let statusId of statusIds) {
deleteFromCache(statusesCache, instanceName, statusId)
}
for (let notificationId of notificationIds) {
deleteFromCache(notificationsCache, instanceName, notificationId)
}
const db = await getDatabase(instanceName)
let storeNames = [
STATUSES_STORE,
STATUS_TIMELINES_STORE,
NOTIFICATIONS_STORE,
NOTIFICATION_TIMELINES_STORE,
PINNED_STATUSES_STORE,
THREADS_STORE
]
await dbPromise(db, storeNames, 'readwrite', (stores) => {
let [
statusesStore,
statusTimelinesStore,
notificationsStore,
notificationTimelinesStore,
pinnedStatusesStore,
threadsStore
] = stores
function deleteStatus (statusId) {
statusesStore.delete(statusId)
deleteAll(
pinnedStatusesStore,
pinnedStatusesStore.index('statusId'),
IDBKeyRange.only(statusId)
)
deleteAll(
statusTimelinesStore,
statusTimelinesStore.index('statusId'),
IDBKeyRange.only(statusId)
)
deleteAll(
threadsStore,
threadsStore.index('statusId'),
IDBKeyRange.only(statusId)
)
deleteAll(
threadsStore,
threadsStore,
createThreadKeyRange(statusId)
)
}
function deleteNotification (notificationId) {
notificationsStore.delete(notificationId)
deleteAll(
notificationTimelinesStore,
notificationTimelinesStore.index('notificationId'),
IDBKeyRange.only(notificationId)
)
}
for (let statusId of statusIds) {
deleteStatus(statusId)
}
for (let notificationId of notificationIds) {
deleteNotification(notificationId)
}
})
}

View file

@ -0,0 +1,5 @@
export function fetchAccount (accountsStore, id, callback) {
accountsStore.get(id).onsuccess = e => {
callback(e.target.result)
}
}

View file

@ -0,0 +1,18 @@
import { fetchAccount } from './fetchAccount'
import { ACCOUNT_ID, STATUS_ID } from '../constants'
import { fetchStatus } from './fetchStatus'
export function fetchNotification (notificationsStore, statusesStore, accountsStore, id, callback) {
notificationsStore.get(id).onsuccess = e => {
let notification = e.target.result
callback(notification)
fetchAccount(accountsStore, notification[ACCOUNT_ID], account => {
notification.account = account
})
if (notification[STATUS_ID]) {
fetchStatus(statusesStore, accountsStore, notification[STATUS_ID], status => {
notification.status = status
})
}
}
}

View file

@ -0,0 +1,17 @@
import { fetchAccount } from './fetchAccount'
import { ACCOUNT_ID, REBLOG_ID } from '../constants'
export function fetchStatus (statusesStore, accountsStore, id, callback) {
statusesStore.get(id).onsuccess = e => {
let status = e.target.result
callback(status)
fetchAccount(accountsStore, status[ACCOUNT_ID], account => {
status.account = account
})
if (status[REBLOG_ID]) {
fetchStatus(statusesStore, accountsStore, status[REBLOG_ID], reblog => {
status.reblog = reblog
})
}
}
}

View file

@ -0,0 +1,37 @@
import { dbPromise, getDatabase } from '../databaseLifecycle'
import { getInCache, hasInCache, notificationsCache, setInCache, statusesCache } from '../cache'
import {
ACCOUNTS_STORE,
NOTIFICATIONS_STORE,
STATUSES_STORE
} from '../constants'
import { fetchStatus } from './fetchStatus'
import { fetchNotification } from './fetchNotification'
export async function getStatus (instanceName, id) {
if (hasInCache(statusesCache, instanceName, id)) {
return getInCache(statusesCache, instanceName, id)
}
const db = await getDatabase(instanceName)
let storeNames = [STATUSES_STORE, ACCOUNTS_STORE]
let result = await dbPromise(db, storeNames, 'readonly', (stores, callback) => {
let [ statusesStore, accountsStore ] = stores
fetchStatus(statusesStore, accountsStore, id, callback)
})
setInCache(statusesCache, instanceName, id, result)
return result
}
export async function getNotification (instanceName, id) {
if (hasInCache(notificationsCache, instanceName, id)) {
return getInCache(notificationsCache, instanceName, id)
}
const db = await getDatabase(instanceName)
let storeNames = [NOTIFICATIONS_STORE, STATUSES_STORE, ACCOUNTS_STORE]
let result = await dbPromise(db, storeNames, 'readonly', (stores, callback) => {
let [ notificationsStore, statusesStore, accountsStore ] = stores
fetchNotification(notificationsStore, statusesStore, accountsStore, id, callback)
})
setInCache(notificationsCache, instanceName, id, result)
return result
}

View file

@ -0,0 +1,122 @@
import difference from 'lodash-es/difference'
import times from 'lodash-es/times'
import { cloneForStorage } from '../helpers'
import { dbPromise, getDatabase } from '../databaseLifecycle'
import { accountsCache, notificationsCache, setInCache, statusesCache } from '../cache'
import { scheduleCleanup } from '../cleanup'
import {
ACCOUNTS_STORE,
NOTIFICATION_TIMELINES_STORE,
NOTIFICATIONS_STORE,
STATUS_TIMELINES_STORE,
STATUSES_STORE,
THREADS_STORE
} from '../constants'
import {
createThreadId,
createThreadKeyRange,
createTimelineId
} from '../keys'
import { cacheStatus } from './cacheStatus'
export function putStatus (statusesStore, status) {
statusesStore.put(cloneForStorage(status))
}
export function putAccount (accountsStore, account) {
accountsStore.put(cloneForStorage(account))
}
export function putNotification (notificationsStore, notification) {
notificationsStore.put(cloneForStorage(notification))
}
export function storeAccount (accountsStore, account) {
putAccount(accountsStore, account)
}
export function storeStatus (statusesStore, accountsStore, status) {
putStatus(statusesStore, status)
putAccount(accountsStore, status.account)
if (status.reblog) {
putStatus(statusesStore, status.reblog)
putAccount(accountsStore, status.reblog.account)
}
}
export function storeNotification (notificationsStore, statusesStore, accountsStore, notification) {
if (notification.status) {
storeStatus(statusesStore, accountsStore, notification.status)
}
storeAccount(accountsStore, notification.account)
putNotification(notificationsStore, notification)
}
export async function insertTimelineNotifications (instanceName, timeline, notifications) {
for (let notification of notifications) {
setInCache(notificationsCache, instanceName, notification.id, notification)
setInCache(accountsCache, instanceName, notification.account.id, notification.account)
if (notification.status) {
setInCache(statusesCache, instanceName, notification.status.id, notification.status)
}
}
const db = await getDatabase(instanceName)
let storeNames = [NOTIFICATION_TIMELINES_STORE, NOTIFICATIONS_STORE, ACCOUNTS_STORE, STATUSES_STORE]
await dbPromise(db, storeNames, 'readwrite', (stores) => {
let [ timelineStore, notificationsStore, accountsStore, statusesStore ] = stores
for (let notification of notifications) {
storeNotification(notificationsStore, statusesStore, accountsStore, notification)
timelineStore.put(notification.id, createTimelineId(timeline, notification.id))
}
})
}
export async function insertTimelineStatuses (instanceName, timeline, statuses) {
for (let status of statuses) {
cacheStatus(status, instanceName)
}
const db = await getDatabase(instanceName)
let storeNames = [STATUS_TIMELINES_STORE, STATUSES_STORE, ACCOUNTS_STORE]
await dbPromise(db, storeNames, 'readwrite', (stores) => {
let [ timelineStore, statusesStore, accountsStore ] = stores
for (let status of statuses) {
storeStatus(statusesStore, accountsStore, status)
timelineStore.put(status.id, createTimelineId(timeline, status.id))
}
})
}
export async function insertStatusThread (instanceName, statusId, statuses) {
for (let status of statuses) {
cacheStatus(status, instanceName)
}
const db = await getDatabase(instanceName)
let storeNames = [THREADS_STORE, STATUSES_STORE, ACCOUNTS_STORE]
await dbPromise(db, storeNames, 'readwrite', (stores) => {
let [ threadsStore, statusesStore, accountsStore ] = stores
threadsStore.getAllKeys(createThreadKeyRange(statusId)).onsuccess = e => {
let existingKeys = e.target.result
let newKeys = times(statuses.length, i => createThreadId(statusId, i))
let keysToDelete = difference(existingKeys, newKeys)
for (let key of keysToDelete) {
threadsStore.delete(key)
}
}
statuses.forEach((otherStatus, i) => {
storeStatus(statusesStore, accountsStore, otherStatus)
threadsStore.put(otherStatus.id, createThreadId(statusId, i))
})
})
}
export async function insertTimelineItems (instanceName, timeline, timelineItems) {
/* no await */ scheduleCleanup()
if (timeline === 'notifications') {
return insertTimelineNotifications(instanceName, timeline, timelineItems)
} else if (timeline.startsWith('status/')) {
let statusId = timeline.split('/').slice(-1)[0]
return insertStatusThread(instanceName, statusId, timelineItems)
} else {
return insertTimelineStatuses(instanceName, timeline, timelineItems)
}
}

View file

@ -0,0 +1,27 @@
import { dbPromise, getDatabase } from '../databaseLifecycle'
import { STATUSES_STORE, STATUS_ID, REBLOG_ID, NOTIFICATIONS_STORE } from '../constants'
export async function getReblogsForStatus (instanceName, id) {
const db = await getDatabase(instanceName)
await dbPromise(db, STATUSES_STORE, 'readonly', (statusesStore, callback) => {
statusesStore.index(REBLOG_ID).getAll(IDBKeyRange.only(id)).onsuccess = e => {
callback(e.target.result)
}
})
}
export async function getNotificationIdsForStatuses (instanceName, statusIds) {
const db = await getDatabase(instanceName)
return dbPromise(db, NOTIFICATIONS_STORE, 'readonly', (notificationsStore, callback) => {
let res = []
callback(res)
statusIds.forEach(statusId => {
let req = notificationsStore.index(STATUS_ID).getAllKeys(IDBKeyRange.only(statusId))
req.onsuccess = e => {
for (let id of e.target.result) {
res.push(id)
}
}
})
})
}

View file

@ -0,0 +1,92 @@
import { dbPromise, getDatabase } from '../databaseLifecycle'
import {
ACCOUNTS_STORE,
NOTIFICATION_TIMELINES_STORE,
NOTIFICATIONS_STORE,
STATUS_TIMELINES_STORE,
STATUSES_STORE,
THREADS_STORE
} from '../constants'
import {
createThreadKeyRange,
createTimelineKeyRange
} from '../keys'
import { fetchStatus } from './fetchStatus'
import { fetchNotification } from './fetchNotification'
export async function getNotificationTimeline (instanceName, timeline, maxId, limit) {
let storeNames = [NOTIFICATION_TIMELINES_STORE, NOTIFICATIONS_STORE, STATUSES_STORE, ACCOUNTS_STORE]
const db = await getDatabase(instanceName)
return dbPromise(db, storeNames, 'readonly', (stores, callback) => {
let [ timelineStore, notificationsStore, statusesStore, accountsStore ] = stores
let keyRange = createTimelineKeyRange(timeline, maxId)
timelineStore.getAll(keyRange, limit).onsuccess = e => {
let timelineResults = e.target.result
let res = new Array(timelineResults.length)
timelineResults.forEach((notificationId, i) => {
fetchNotification(notificationsStore, statusesStore, accountsStore, notificationId, notification => {
res[i] = notification
})
})
callback(res)
}
})
}
export async function getStatusTimeline (instanceName, timeline, maxId, limit) {
let storeNames = [STATUS_TIMELINES_STORE, STATUSES_STORE, ACCOUNTS_STORE]
const db = await getDatabase(instanceName)
return dbPromise(db, storeNames, 'readonly', (stores, callback) => {
let [ timelineStore, statusesStore, accountsStore ] = stores
let getReq = timelineStore.getAll(createTimelineKeyRange(timeline, maxId), limit)
getReq.onsuccess = e => {
let timelineResults = e.target.result
let res = new Array(timelineResults.length)
timelineResults.forEach((statusId, i) => {
fetchStatus(statusesStore, accountsStore, statusId, status => {
res[i] = status
})
})
callback(res)
}
})
}
export async function getStatusThread (instanceName, statusId) {
let storeNames = [THREADS_STORE, STATUSES_STORE, ACCOUNTS_STORE]
const db = await getDatabase(instanceName)
return dbPromise(db, storeNames, 'readonly', (stores, callback) => {
let [ threadsStore, statusesStore, accountsStore ] = stores
let keyRange = createThreadKeyRange(statusId)
threadsStore.getAll(keyRange).onsuccess = e => {
let thread = e.target.result
if (thread.length) {
let res = new Array(thread.length)
callback(res)
thread.forEach((otherStatusId, i) => {
fetchStatus(statusesStore, accountsStore, otherStatusId, status => {
res[i] = status
})
})
} else {
// thread not cached; just make a "fake" thread with only one status in it
fetchStatus(statusesStore, accountsStore, statusId, status => {
let res = [status]
callback(res)
})
}
}
})
}
export async function getTimeline (instanceName, timeline, maxId = null, limit = 20) {
if (timeline === 'notifications') {
return getNotificationTimeline(instanceName, timeline, maxId, limit)
} else if (timeline.startsWith('status/')) {
let statusId = timeline.split('/').slice(-1)[0]
return getStatusThread(instanceName, statusId)
} else {
return getStatusTimeline(instanceName, timeline, maxId, limit)
}
}

View file

@ -0,0 +1,40 @@
import { cacheStatus } from './cacheStatus'
import { getDatabase, dbPromise } from '../databaseLifecycle'
import { PINNED_STATUSES_STORE, STATUSES_STORE, ACCOUNTS_STORE } from '../constants'
import { createPinnedStatusId, createPinnedStatusKeyRange } from '../keys'
import { storeStatus } from './insertion'
import { fetchStatus } from './fetchStatus'
export async function insertPinnedStatuses (instanceName, accountId, statuses) {
for (let status of statuses) {
cacheStatus(status, instanceName)
}
const db = await getDatabase(instanceName)
let storeNames = [PINNED_STATUSES_STORE, STATUSES_STORE, ACCOUNTS_STORE]
await dbPromise(db, storeNames, 'readwrite', (stores) => {
let [ pinnedStatusesStore, statusesStore, accountsStore ] = stores
statuses.forEach((status, i) => {
storeStatus(statusesStore, accountsStore, status)
pinnedStatusesStore.put(status.id, createPinnedStatusId(accountId, i))
})
})
}
export async function getPinnedStatuses (instanceName, accountId) {
let storeNames = [PINNED_STATUSES_STORE, STATUSES_STORE, ACCOUNTS_STORE]
const db = await getDatabase(instanceName)
return dbPromise(db, storeNames, 'readonly', (stores, callback) => {
let [ pinnedStatusesStore, statusesStore, accountsStore ] = stores
let keyRange = createPinnedStatusKeyRange(accountId)
pinnedStatusesStore.getAll(keyRange).onsuccess = e => {
let pinnedResults = e.target.result
let res = new Array(pinnedResults.length)
pinnedResults.forEach((statusId, i) => {
fetchStatus(statusesStore, accountsStore, statusId, status => {
res[i] = status
})
})
callback(res)
}
})
}

View file

@ -0,0 +1,41 @@
import { dbPromise, getDatabase } from '../databaseLifecycle'
import { getInCache, hasInCache, statusesCache } from '../cache'
import { STATUSES_STORE } from '../constants'
import { cacheStatus } from './cacheStatus'
import { putStatus } from './insertion'
//
// update statuses
//
async function updateStatus (instanceName, statusId, updateFunc) {
const db = await getDatabase(instanceName)
if (hasInCache(statusesCache, instanceName, statusId)) {
let status = getInCache(statusesCache, instanceName, statusId)
updateFunc(status)
cacheStatus(status, instanceName)
}
return dbPromise(db, STATUSES_STORE, 'readwrite', (statusesStore) => {
statusesStore.get(statusId).onsuccess = e => {
let status = e.target.result
updateFunc(status)
putStatus(statusesStore, status)
}
})
}
export async function setStatusFavorited (instanceName, statusId, favorited) {
return updateStatus(instanceName, statusId, status => {
let delta = (favorited ? 1 : 0) - (status.favourited ? 1 : 0)
status.favourited = favorited
status.favourites_count = (status.favourites_count || 0) + delta
})
}
export async function setStatusReblogged (instanceName, statusId, reblogged) {
return updateStatus(instanceName, statusId, status => {
let delta = (reblogged ? 1 : 0) - (status.reblogged ? 1 : 0)
status.reblogged = reblogged
status.reblogs_count = (status.reblogs_count || 0) + delta
})
}