fix offline threads
This commit is contained in:
parent
f0d05b7560
commit
e3b6a9b758
|
@ -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)
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in New Issue