Refactor database for better code-splitting (#144)
This commit is contained in:
parent
8fb00a961c
commit
b231466fff
|
@ -1,11 +1,16 @@
|
|||
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'
|
||||
|
||||
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 => {
|
||||
database.setAccount(instanceName, account)
|
||||
/* no await */ setAccountInDatabase(instanceName, account)
|
||||
return account
|
||||
})
|
||||
|
||||
|
@ -22,9 +27,9 @@ async function updateAccount (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 => {
|
||||
database.setRelationship(instanceName, relationship)
|
||||
/* no await */ setRelationshipInDatabase(instanceName, relationship)
|
||||
return relationship
|
||||
})
|
||||
try {
|
||||
|
|
|
@ -2,10 +2,10 @@ import { getAccessTokenFromAuthCode, registerApplication, generateAuthLink } fro
|
|||
import { getInstanceInfo } from '../_api/instance'
|
||||
import { goto } from 'sapper/runtime.js'
|
||||
import { switchToTheme } from '../_utils/themeEngine'
|
||||
import { database } from '../_database/database'
|
||||
import { store } from '../_store/store'
|
||||
import { updateVerifyCredentialsForInstance } from './instances'
|
||||
import { updateCustomEmojiForInstance } from './emoji'
|
||||
import { setInstanceInfo as setInstanceInfoInDatabase } from '../_database/meta'
|
||||
|
||||
const REDIRECT_URI = (typeof location !== 'undefined'
|
||||
? location.origin : 'https://pinafore.social') + '/settings/instances/add'
|
||||
|
@ -20,7 +20,7 @@ async function redirectToOauth () {
|
|||
}
|
||||
let registrationPromise = registerApplication(instanceName, REDIRECT_URI)
|
||||
let instanceInfo = await getInstanceInfo(instanceName)
|
||||
await database.setInstanceInfo(instanceName, instanceInfo) // cache for later
|
||||
await setInstanceInfoInDatabase(instanceName, instanceInfo) // cache for later
|
||||
let instanceData = await registrationPromise
|
||||
store.set({
|
||||
currentRegisteredInstanceName: instanceName,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import throttle from 'lodash-es/throttle'
|
||||
import { database } from '../_database/database'
|
||||
import { mark, stop } from '../_utils/marks'
|
||||
import { store } from '../_store/store'
|
||||
import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
|
||||
|
@ -7,6 +6,9 @@ import uniqBy from 'lodash-es/uniqBy'
|
|||
import uniq from 'lodash-es/uniq'
|
||||
import isEqual from 'lodash-es/isEqual'
|
||||
import { isMobile } from '../_utils/isMobile'
|
||||
import {
|
||||
insertTimelineItems as insertTimelineItemsInDatabase
|
||||
} from '../_database/timelines/insertion'
|
||||
|
||||
const STREAMING_THROTTLE_DELAY = 3000
|
||||
|
||||
|
@ -28,7 +30,7 @@ async function insertUpdatesIntoTimeline (instanceName, timelineName, updates) {
|
|||
return
|
||||
}
|
||||
|
||||
await database.insertTimelineItems(instanceName, timelineName, updates)
|
||||
await insertTimelineItemsInDatabase(instanceName, timelineName, updates)
|
||||
|
||||
let itemIdsToAdd = store.getForTimeline(instanceName, timelineName, 'itemIdsToAdd') || []
|
||||
let newItemIdsToAdd = uniq([].concat(itemIdsToAdd).concat(updates.map(_ => _.id)))
|
||||
|
|
|
@ -2,13 +2,13 @@ import { store } from '../_store/store'
|
|||
import { toast } from '../_utils/toast'
|
||||
import { postStatus as postStatusToServer } from '../_api/statuses'
|
||||
import { addStatusOrNotification } from './addStatusOrNotification'
|
||||
import { database } from '../_database/database'
|
||||
import { getStatus as getStatusFromDatabase } from '../_database/timelines/getStatusOrNotification'
|
||||
import { emit } from '../_utils/eventBus'
|
||||
import { putMediaDescription } from '../_api/media'
|
||||
|
||||
export async function insertHandleForReply (statusId) {
|
||||
let instanceName = store.get('currentInstance')
|
||||
let status = await database.getStatus(instanceName, statusId)
|
||||
let status = await getStatusFromDatabase(instanceName, statusId)
|
||||
let verifyCredentials = store.get('currentVerifyCredentials')
|
||||
let originalStatus = status.reblog || status
|
||||
let accounts = [originalStatus.account].concat(originalStatus.mentions || [])
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { getIdsThatRebloggedThisStatus, getNotificationIdsForStatuses } from './statuses'
|
||||
import { store } from '../_store/store'
|
||||
import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
|
||||
import { database } from '../_database/database'
|
||||
import forEach from 'lodash-es/forEach'
|
||||
import isEqual from 'lodash-es/isEqual'
|
||||
import {
|
||||
deleteStatusesAndNotifications as deleteStatusesAndNotificationsFromDatabase
|
||||
} from '../_database/timelines/deletion'
|
||||
|
||||
function filterItemIdsFromTimelines (instanceName, timelineFilter, idFilter) {
|
||||
let keys = ['timelineItemIds', 'itemIdsToAdd']
|
||||
|
@ -43,7 +45,7 @@ function deleteNotificationIdsFromStore (instanceName, idsToDelete) {
|
|||
async function deleteStatusesAndNotifications (instanceName, statusIdsToDelete, notificationIdsToDelete) {
|
||||
deleteStatusIdsFromStore(instanceName, statusIdsToDelete)
|
||||
deleteNotificationIdsFromStore(instanceName, notificationIdsToDelete)
|
||||
await database.deleteStatusesAndNotifications(instanceName, statusIdsToDelete, notificationIdsToDelete)
|
||||
await deleteStatusesAndNotificationsFromDatabase(instanceName, statusIdsToDelete, notificationIdsToDelete)
|
||||
}
|
||||
|
||||
async function doDeleteStatus (instanceName, statusId) {
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
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 { store } from '../_store/store'
|
||||
import { substring } from 'stringz'
|
||||
|
@ -7,8 +10,8 @@ import { substring } from 'stringz'
|
|||
export async function updateCustomEmojiForInstance (instanceName) {
|
||||
await cacheFirstUpdateAfter(
|
||||
() => getCustomEmoji(instanceName),
|
||||
() => database.getCustomEmoji(instanceName),
|
||||
emoji => database.setCustomEmoji(instanceName, emoji),
|
||||
() => getCustomEmojiFromDatabase(instanceName),
|
||||
emoji => setCustomEmojiInDatabase(instanceName, emoji),
|
||||
emoji => {
|
||||
let customEmoji = store.get('customEmoji')
|
||||
customEmoji[instanceName] = emoji
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { favoriteStatus, unfavoriteStatus } from '../_api/favorite'
|
||||
import { store } from '../_store/store'
|
||||
import { database } from '../_database/database'
|
||||
import { toast } from '../_utils/toast'
|
||||
import {
|
||||
setStatusFavorited as setStatusFavoritedInDatabase
|
||||
} from '../_database/timelines/updateStatus'
|
||||
|
||||
export async function setFavorited (statusId, favorited) {
|
||||
if (!store.get('online')) {
|
||||
|
@ -16,7 +18,7 @@ export async function setFavorited (statusId, favorited) {
|
|||
store.setStatusFavorited(instanceName, statusId, favorited) // optimistic update
|
||||
try {
|
||||
await networkPromise
|
||||
await database.setStatusFavorited(instanceName, statusId, favorited)
|
||||
await setStatusFavoritedInDatabase(instanceName, statusId, favorited)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say(`Failed to ${favorited ? 'favorite' : 'unfavorite'}. ` + (e.message || ''))
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { store } from '../_store/store'
|
||||
import { followAccount, unfollowAccount } from '../_api/follow'
|
||||
import { database } from '../_database/database'
|
||||
import { toast } from '../_utils/toast'
|
||||
import { updateProfileAndRelationship } from './accounts'
|
||||
import {
|
||||
getRelationship as getRelationshipFromDatabase
|
||||
} from '../_database/accountsAndRelationships'
|
||||
|
||||
export async function setAccountFollowed (accountId, follow, toastOnSuccess) {
|
||||
let instanceName = store.get('currentInstance')
|
||||
|
@ -15,7 +17,7 @@ export async function setAccountFollowed (accountId, follow, toastOnSuccess) {
|
|||
account = await unfollowAccount(instanceName, accessToken, accountId)
|
||||
}
|
||||
await updateProfileAndRelationship(accountId)
|
||||
let relationship = await database.getRelationship(instanceName, accountId)
|
||||
let relationship = await getRelationshipFromDatabase(instanceName, accountId)
|
||||
if (toastOnSuccess) {
|
||||
if (follow) {
|
||||
if (account.locked && relationship.requested) {
|
||||
|
|
|
@ -2,10 +2,16 @@ import { getVerifyCredentials } from '../_api/user'
|
|||
import { store } from '../_store/store'
|
||||
import { switchToTheme } from '../_utils/themeEngine'
|
||||
import { toast } from '../_utils/toast'
|
||||
import { database } from '../_database/database'
|
||||
import { goto } from 'sapper/runtime.js'
|
||||
import { cacheFirstUpdateAfter } from '../_utils/sync'
|
||||
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) {
|
||||
let instanceThemes = store.get('instanceThemes')
|
||||
|
@ -53,7 +59,7 @@ export async function logOutOfInstance (instanceName) {
|
|||
store.save()
|
||||
toast.say(`Logged out of ${instanceName}`)
|
||||
switchToTheme(instanceThemes[newInstance] || 'default')
|
||||
await database.clearDatabaseForInstance(instanceName)
|
||||
await clearDatabaseForInstance(instanceName)
|
||||
goto('/settings/instances')
|
||||
}
|
||||
|
||||
|
@ -68,8 +74,8 @@ export async function updateVerifyCredentialsForInstance (instanceName) {
|
|||
let accessToken = loggedInInstances[instanceName].access_token
|
||||
await cacheFirstUpdateAfter(
|
||||
() => getVerifyCredentials(instanceName, accessToken),
|
||||
() => database.getInstanceVerifyCredentials(instanceName),
|
||||
verifyCredentials => database.setInstanceVerifyCredentials(instanceName, verifyCredentials),
|
||||
() => getInstanceVerifyCredentialsFromDatabase(instanceName),
|
||||
verifyCredentials => setInstanceVerifyCredentialsInDatabase(instanceName, verifyCredentials),
|
||||
verifyCredentials => setStoreVerifyCredentials(instanceName, verifyCredentials)
|
||||
)
|
||||
}
|
||||
|
@ -81,8 +87,8 @@ export async function updateVerifyCredentialsForCurrentInstance () {
|
|||
export async function updateInstanceInfo (instanceName) {
|
||||
await cacheFirstUpdateAfter(
|
||||
() => getInstanceInfo(instanceName),
|
||||
() => database.getInstanceInfo(instanceName),
|
||||
info => database.setInstanceInfo(instanceName, info),
|
||||
() => getInstanceInfoFromDatabase(instanceName),
|
||||
info => setInstanceInfoInDatabase(instanceName, info),
|
||||
info => {
|
||||
let instanceInfos = store.get('instanceInfos')
|
||||
instanceInfos[instanceName] = info
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { store } from '../_store/store'
|
||||
import { database } from '../_database/database'
|
||||
import { getLists } from '../_api/lists'
|
||||
import { cacheFirstUpdateAfter } from '../_utils/sync'
|
||||
import {
|
||||
getLists as getListsFromDatabase,
|
||||
setLists as setListsInDatabase
|
||||
} from '../_database/meta'
|
||||
|
||||
export async function updateLists () {
|
||||
let instanceName = store.get('currentInstance')
|
||||
|
@ -9,8 +12,8 @@ export async function updateLists () {
|
|||
|
||||
await cacheFirstUpdateAfter(
|
||||
() => getLists(instanceName, accessToken),
|
||||
() => database.getLists(instanceName),
|
||||
lists => database.setLists(instanceName, lists),
|
||||
() => getListsFromDatabase(instanceName),
|
||||
lists => setListsInDatabase(instanceName, lists),
|
||||
lists => {
|
||||
let instanceLists = store.get('instanceLists')
|
||||
instanceLists[instanceName] = lists
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import { store } from '../_store/store'
|
||||
import { cacheFirstUpdateAfter } from '../_utils/sync'
|
||||
import { getPinnedStatuses } from '../_api/pinnedStatuses'
|
||||
import { database } from '../_database/database'
|
||||
import {
|
||||
getPinnedStatuses as getPinnedStatusesFromDatabase,
|
||||
insertPinnedStatuses as insertPinnedStatusesInDatabase
|
||||
} from '../_database/timelines/pinnedStatuses'
|
||||
import {
|
||||
getPinnedStatuses
|
||||
} from '../_api/pinnedStatuses'
|
||||
|
||||
export async function updatePinnedStatusesForAccount (accountId) {
|
||||
let instanceName = store.get('currentInstance')
|
||||
|
@ -9,8 +14,8 @@ export async function updatePinnedStatusesForAccount (accountId) {
|
|||
|
||||
await cacheFirstUpdateAfter(
|
||||
() => getPinnedStatuses(instanceName, accessToken, accountId),
|
||||
() => database.getPinnedStatuses(instanceName, accountId),
|
||||
statuses => database.insertPinnedStatuses(instanceName, accountId, statuses),
|
||||
() => getPinnedStatusesFromDatabase(instanceName, accountId),
|
||||
statuses => insertPinnedStatusesInDatabase(instanceName, accountId, statuses),
|
||||
statuses => {
|
||||
let $pinnedStatuses = store.get('pinnedStatuses')
|
||||
$pinnedStatuses[instanceName] = $pinnedStatuses[instanceName] || {}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { store } from '../_store/store'
|
||||
import { database } from '../_database/database'
|
||||
import { toast } from '../_utils/toast'
|
||||
import { reblogStatus, unreblogStatus } from '../_api/reblog'
|
||||
import { setStatusReblogged as setStatusRebloggedInDatabase } from '../_database/timelines/updateStatus'
|
||||
|
||||
export async function setReblogged (statusId, reblogged) {
|
||||
if (!store.get('online')) {
|
||||
|
@ -16,7 +16,7 @@ export async function setReblogged (statusId, reblogged) {
|
|||
store.setStatusReblogged(instanceName, statusId, reblogged) // optimistic update
|
||||
try {
|
||||
await networkPromise
|
||||
await database.setStatusReblogged(instanceName, statusId, reblogged)
|
||||
await setStatusRebloggedInDatabase(instanceName, statusId, reblogged)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say(`Failed to ${reblogged ? 'boost' : 'unboost'}. ` + (e.message || ''))
|
||||
|
|
|
@ -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) {
|
||||
let status = await database.getStatus(instanceName, statusId)
|
||||
let status = await getStatusFromDatabase(instanceName, statusId)
|
||||
return status.reblog && status.reblog.id
|
||||
}
|
||||
|
||||
|
@ -13,9 +19,9 @@ export async function getIdsThatTheseStatusesReblogged (instanceName, statusIds)
|
|||
}
|
||||
|
||||
export async function getIdsThatRebloggedThisStatus (instanceName, statusId) {
|
||||
return database.getReblogsForStatus(instanceName, statusId)
|
||||
return getReblogsForStatusFromDatabase(instanceName, statusId)
|
||||
}
|
||||
|
||||
export async function getNotificationIdsForStatuses (instanceName, statusIds) {
|
||||
return database.getNotificationIdsForStatuses(instanceName, statusIds)
|
||||
return getNotificationIdsForStatusesFromDatabase(instanceName, statusIds)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import { store } from '../_store/store'
|
||||
import { database } from '../_database/database'
|
||||
import { getTimeline } from '../_api/timelines'
|
||||
import { toast } from '../_utils/toast'
|
||||
import { mark, stop } from '../_utils/marks'
|
||||
import { mergeArrays } from '../_utils/arrays'
|
||||
import { byItemIds } from '../_utils/sorting'
|
||||
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
|
||||
|
||||
|
@ -14,16 +19,16 @@ async function fetchTimelineItems (instanceName, accessToken, timelineName, last
|
|||
let items
|
||||
let stale = false
|
||||
if (!online) {
|
||||
items = await database.getTimeline(instanceName, timelineName, lastTimelineItemId, FETCH_LIMIT)
|
||||
items = await getTimelineFromDatabase(instanceName, timelineName, lastTimelineItemId, FETCH_LIMIT)
|
||||
stale = true
|
||||
} else {
|
||||
try {
|
||||
items = await getTimeline(instanceName, accessToken, timelineName, lastTimelineItemId, FETCH_LIMIT)
|
||||
/* no await */ database.insertTimelineItems(instanceName, timelineName, items)
|
||||
/* no await */ insertTimelineItemsInDatabase(instanceName, timelineName, items)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,12 +39,14 @@
|
|||
</style>
|
||||
<script>
|
||||
import { store } from '../../_store/store'
|
||||
import { database } from '../../_database/database'
|
||||
import { insertUsername } from '../../_actions/compose'
|
||||
import { insertEmojiAtPosition } from '../../_actions/emoji'
|
||||
import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
|
||||
import { once } from '../../_utils/once'
|
||||
import ComposeAutosuggestionList from './ComposeAutosuggestionList.html'
|
||||
import {
|
||||
searchAccountsByUsername as searchAccountsByUsernameInDatabase
|
||||
} from '../../_database/accountsAndRelationships'
|
||||
|
||||
const SEARCH_RESULTS_LIMIT = 4
|
||||
const DATABASE_SEARCH_RESULTS_LIMIT = 30
|
||||
|
@ -116,7 +118,7 @@
|
|||
async searchAccounts(searchText) {
|
||||
searchText = searchText.substring(1)
|
||||
let currentInstance = this.store.get('currentInstance')
|
||||
let results = await database.searchAccountsByUsername(
|
||||
let results = await searchAccountsByUsernameInDatabase(
|
||||
currentInstance, searchText, DATABASE_SEARCH_RESULTS_LIMIT)
|
||||
return results.slice(0, SEARCH_RESULTS_LIMIT)
|
||||
},
|
||||
|
|
|
@ -45,7 +45,10 @@
|
|||
import VirtualList from '../virtualList/VirtualList.html'
|
||||
import PseudoVirtualList from '../pseudoVirtualList/PseudoVirtualList.html'
|
||||
import { timelines } from '../../_static/timelines'
|
||||
import { database } from '../../_database/database'
|
||||
import {
|
||||
getStatus as getStatusFromDatabase,
|
||||
getNotification as getNotificationFromDatabase
|
||||
} from '../../_database/timelines/getStatusOrNotification'
|
||||
import {
|
||||
fetchTimelineItemsOnScrollToBottom,
|
||||
setupTimeline,
|
||||
|
@ -87,9 +90,9 @@
|
|||
timelineValue
|
||||
}
|
||||
if (timelineType === 'notifications') {
|
||||
res.notification = await database.getNotification($currentInstance, itemId)
|
||||
res.notification = await getNotificationFromDatabase($currentInstance, itemId)
|
||||
} else {
|
||||
res.status = await database.getStatus($currentInstance, itemId)
|
||||
res.status = await getStatusFromDatabase($currentInstance, itemId)
|
||||
}
|
||||
return res
|
||||
},
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
import * as database from './databaseCore'
|
||||
|
||||
export { database }
|
|
@ -1,4 +0,0 @@
|
|||
export * from './accountsAndRelationships'
|
||||
export * from './clear'
|
||||
export * from './meta'
|
||||
export * from './timelines'
|
|
@ -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
|
||||
})
|
||||
}
|
11
routes/_database/timelines/cacheStatus.js
Normal file
11
routes/_database/timelines/cacheStatus.js
Normal 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)
|
||||
}
|
||||
}
|
84
routes/_database/timelines/deletion.js
Normal file
84
routes/_database/timelines/deletion.js
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
5
routes/_database/timelines/fetchAccount.js
Normal file
5
routes/_database/timelines/fetchAccount.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
export function fetchAccount (accountsStore, id, callback) {
|
||||
accountsStore.get(id).onsuccess = e => {
|
||||
callback(e.target.result)
|
||||
}
|
||||
}
|
18
routes/_database/timelines/fetchNotification.js
Normal file
18
routes/_database/timelines/fetchNotification.js
Normal 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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
17
routes/_database/timelines/fetchStatus.js
Normal file
17
routes/_database/timelines/fetchStatus.js
Normal 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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
37
routes/_database/timelines/getStatusOrNotification.js
Normal file
37
routes/_database/timelines/getStatusOrNotification.js
Normal 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
|
||||
}
|
122
routes/_database/timelines/insertion.js
Normal file
122
routes/_database/timelines/insertion.js
Normal 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)
|
||||
}
|
||||
}
|
27
routes/_database/timelines/lookup.js
Normal file
27
routes/_database/timelines/lookup.js
Normal 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
92
routes/_database/timelines/pagination.js
Normal file
92
routes/_database/timelines/pagination.js
Normal 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)
|
||||
}
|
||||
}
|
40
routes/_database/timelines/pinnedStatuses.js
Normal file
40
routes/_database/timelines/pinnedStatuses.js
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
41
routes/_database/timelines/updateStatus.js
Normal file
41
routes/_database/timelines/updateStatus.js
Normal 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
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue