fix offline threads

This commit is contained in:
Nolan Lawson 2018-03-08 18:31:59 -08:00
parent f0d05b7560
commit e3b6a9b758
4 changed files with 69 additions and 33 deletions

View File

@ -7,6 +7,7 @@ import {
RELATIONSHIPS_STORE, RELATIONSHIPS_STORE,
STATUS_TIMELINES_STORE, STATUS_TIMELINES_STORE,
STATUSES_STORE, STATUSES_STORE,
THREADS_STORE,
TIMESTAMP TIMESTAMP
} from './constants' } from './constants'
import debounce from 'lodash/debounce' import debounce from 'lodash/debounce'
@ -30,12 +31,13 @@ function batchedGetAll (callGetAll, callback) {
nextBatch() nextBatch()
} }
function cleanupStatuses (statusesStore, statusTimelinesStore, cutoff) { function cleanupStatuses (statusesStore, statusTimelinesStore, threadsStore, cutoff) {
batchedGetAll( batchedGetAll(
() => statusesStore.index(TIMESTAMP).getAll(IDBKeyRange.upperBound(cutoff), BATCH_SIZE), () => statusesStore.index(TIMESTAMP).getAll(IDBKeyRange.upperBound(cutoff), BATCH_SIZE),
results => { results => {
results.forEach(result => { results.forEach(result => {
statusesStore.delete(result.id) statusesStore.delete(result.id)
threadsStore.delete(result.id)
let req = statusTimelinesStore.index('statusId').getAll(IDBKeyRange.only(result.id)) let req = statusTimelinesStore.index('statusId').getAll(IDBKeyRange.only(result.id))
req.onsuccess = e => { req.onsuccess = e => {
let results = e.target.result let results = e.target.result
@ -98,7 +100,8 @@ async function cleanup (instanceName) {
NOTIFICATIONS_STORE, NOTIFICATIONS_STORE,
NOTIFICATION_TIMELINES_STORE, NOTIFICATION_TIMELINES_STORE,
ACCOUNTS_STORE, ACCOUNTS_STORE,
RELATIONSHIPS_STORE RELATIONSHIPS_STORE,
THREADS_STORE
] ]
await dbPromise(db, storeNames, 'readwrite', (stores) => { await dbPromise(db, storeNames, 'readwrite', (stores) => {
let [ let [
@ -107,12 +110,13 @@ async function cleanup (instanceName) {
notificationsStore, notificationsStore,
notificationTimelinesStore, notificationTimelinesStore,
accountsStore, accountsStore,
relationshipsStore relationshipsStore,
threadsStore
] = stores ] = stores
let cutoff = Date.now() - TIME_AGO let cutoff = Date.now() - TIME_AGO
cleanupStatuses(statusesStore, statusTimelinesStore, cutoff) cleanupStatuses(statusesStore, statusTimelinesStore, threadsStore, cutoff)
cleanupNotifications(notificationsStore, notificationTimelinesStore, cutoff) cleanupNotifications(notificationsStore, notificationTimelinesStore, cutoff)
cleanupAccounts(accountsStore, cutoff) cleanupAccounts(accountsStore, cutoff)
cleanupRelationships(relationshipsStore, cutoff) cleanupRelationships(relationshipsStore, cutoff)

View File

@ -6,6 +6,7 @@ export const RELATIONSHIPS_STORE = 'relationships'
export const NOTIFICATIONS_STORE = 'notifications' export const NOTIFICATIONS_STORE = 'notifications'
export const NOTIFICATION_TIMELINES_STORE = 'notification_timelines' export const NOTIFICATION_TIMELINES_STORE = 'notification_timelines'
export const PINNED_STATUSES_STORE = 'pinned_statuses' export const PINNED_STATUSES_STORE = 'pinned_statuses'
export const THREADS_STORE = 'threads'
export const TIMESTAMP = '__pinafore_ts' export const TIMESTAMP = '__pinafore_ts'
export const ACCOUNT_ID = '__pinafore_acct_id' export const ACCOUNT_ID = '__pinafore_acct_id'

View File

@ -7,13 +7,13 @@ import {
NOTIFICATIONS_STORE, NOTIFICATIONS_STORE,
NOTIFICATION_TIMELINES_STORE, NOTIFICATION_TIMELINES_STORE,
PINNED_STATUSES_STORE, PINNED_STATUSES_STORE,
TIMESTAMP, REBLOG_ID TIMESTAMP, REBLOG_ID, THREADS_STORE
} from './constants' } from './constants'
const openReqs = {} const openReqs = {}
const databaseCache = {} const databaseCache = {}
const DB_VERSION = 3 const DB_VERSION = 4
export function getDatabase (instanceName) { export function getDatabase (instanceName) {
if (!instanceName) { if (!instanceName) {
@ -55,6 +55,9 @@ export function getDatabase (instanceName) {
if (e.oldVersion < 3) { if (e.oldVersion < 3) {
tx.objectStore(NOTIFICATIONS_STORE).createIndex('statusId', 'statusId') tx.objectStore(NOTIFICATIONS_STORE).createIndex('statusId', 'statusId')
} }
if (e.oldVersion < 4) {
db.createObjectStore(THREADS_STORE, {keyPath: 'id'})
}
} }
req.onsuccess = () => resolve(req.result) req.onsuccess = () => resolve(req.result)
}) })

View File

@ -14,7 +14,7 @@ import {
STATUSES_STORE, STATUSES_STORE,
ACCOUNT_ID, ACCOUNT_ID,
REBLOG_ID, REBLOG_ID,
STATUS_ID STATUS_ID, THREADS_STORE
} from './constants' } from './constants'
function createTimelineKeyRange (timeline, maxId) { function createTimelineKeyRange (timeline, maxId) {
@ -24,14 +24,6 @@ function createTimelineKeyRange (timeline, maxId) {
return IDBKeyRange.bound(start, end, true, true) return IDBKeyRange.bound(start, end, true, true)
} }
// special case for threads these are in chronological order rather than reverse
// chronological order, and we fetch everything all at once rather than paginating
function createKeyRangeForStatusThread (timeline) {
let start = timeline + '\u0000'
let end = timeline + '\u0000\uffff'
return IDBKeyRange.bound(start, end, true, true)
}
function cacheStatus (status, instanceName) { function cacheStatus (status, instanceName) {
setInCache(statusesCache, instanceName, status.id, status) setInCache(statusesCache, instanceName, status.id, status)
setInCache(accountsCache, instanceName, status.account.id, status.account) setInCache(accountsCache, instanceName, status.account.id, status.account)
@ -69,18 +61,9 @@ async function getStatusTimeline (instanceName, timeline, maxId, limit) {
const db = await getDatabase(instanceName) const db = await getDatabase(instanceName)
return dbPromise(db, storeNames, 'readonly', (stores, callback) => { return dbPromise(db, storeNames, 'readonly', (stores, callback) => {
let [ timelineStore, statusesStore, accountsStore ] = stores let [ timelineStore, statusesStore, accountsStore ] = stores
// Status threads are a special case - these are in forward chronological order let getReq = timelineStore.getAll(createTimelineKeyRange(timeline, maxId), limit)
// and we fetch them all at once instead of paginating.
let isStatusThread = timeline.startsWith('status/')
let getReq = isStatusThread
? timelineStore.getAll(createKeyRangeForStatusThread(timeline))
: timelineStore.getAll(createTimelineKeyRange(timeline, maxId), limit)
getReq.onsuccess = e => { getReq.onsuccess = e => {
let timelineResults = e.target.result let timelineResults = e.target.result
if (isStatusThread) {
timelineResults = timelineResults.reverse()
}
let res = new Array(timelineResults.length) let res = new Array(timelineResults.length)
timelineResults.forEach((timelineResult, i) => { timelineResults.forEach((timelineResult, i) => {
fetchStatus(statusesStore, accountsStore, timelineResult.statusId, status => { fetchStatus(statusesStore, accountsStore, timelineResult.statusId, status => {
@ -92,10 +75,33 @@ async function getStatusTimeline (instanceName, timeline, maxId, limit) {
}) })
} }
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
threadsStore.get(statusId).onsuccess = e => {
let thread = e.target.result.thread
let res = new Array(thread.length)
thread.forEach((otherStatusId, i) => {
fetchStatus(statusesStore, accountsStore, otherStatusId, status => {
res[i] = status
})
})
callback(res)
}
})
}
export async function getTimeline (instanceName, timeline, maxId = null, limit = 20) { export async function getTimeline (instanceName, timeline, maxId = null, limit = 20) {
return timeline === 'notifications' if (timeline === 'notifications') {
? getNotificationTimeline(instanceName, timeline, maxId, limit) return getNotificationTimeline(instanceName, timeline, maxId, limit)
: getStatusTimeline(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)
}
} }
// //
@ -177,7 +183,6 @@ function createTimelineId (timeline, id) {
} }
async function insertTimelineNotifications (instanceName, timeline, notifications) { async function insertTimelineNotifications (instanceName, timeline, notifications) {
/* no await */ scheduleCleanup()
for (let notification of notifications) { for (let notification of notifications) {
setInCache(notificationsCache, instanceName, notification.id, notification) setInCache(notificationsCache, instanceName, notification.id, notification)
setInCache(accountsCache, instanceName, notification.account.id, notification.account) setInCache(accountsCache, instanceName, notification.account.id, notification.account)
@ -200,7 +205,6 @@ async function insertTimelineNotifications (instanceName, timeline, notification
} }
async function insertTimelineStatuses (instanceName, timeline, statuses) { async function insertTimelineStatuses (instanceName, timeline, statuses) {
/* no await */ scheduleCleanup()
for (let status of statuses) { for (let status of statuses) {
cacheStatus(status, instanceName) cacheStatus(status, instanceName)
} }
@ -218,10 +222,34 @@ async function insertTimelineStatuses (instanceName, timeline, statuses) {
}) })
} }
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.put({
id: statusId,
thread: statuses.map(_ => _.id)
})
for (let status of statuses) {
storeStatus(statusesStore, accountsStore, status)
}
})
}
export async function insertTimelineItems (instanceName, timeline, timelineItems) { export async function insertTimelineItems (instanceName, timeline, timelineItems) {
return timeline === 'notifications' /* no await */ scheduleCleanup()
? insertTimelineNotifications(instanceName, timeline, timelineItems) if (timeline === 'notifications') {
: insertTimelineStatuses(instanceName, timeline, timelineItems) 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)
}
} }
// //