diff --git a/lib/ops/dm-operations-store-ops.js b/lib/ops/dm-operations-store-ops.js --- a/lib/ops/dm-operations-store-ops.js +++ b/lib/ops/dm-operations-store-ops.js @@ -1,13 +1,22 @@ // @flow +import _mapValues from 'lodash/fp/mapValues.js'; + +import type { BaseStoreOpsHandlers } from './base-ops.js'; import { type QueuedDMOperations, queuedDMOperationConditionType, type QueueDMOpsCondition, type DMOperation, + type QueueDMOpsPayload, + type QueuedDMOperationCondition, + type PruneDMOpsQueuePayload, + assertQueuedDMOperationCondition, + type OperationsQueue, } from '../types/dm-ops.js'; -type Operation = { +// Shimmed DM Operations ops +export type DMOperationEntity = { +id: string, +type: string, +operation: DMOperation, @@ -15,7 +24,7 @@ export type ReplaceDMOperationOperation = { +type: 'replace_dm_operation', - +payload: Operation, + +payload: DMOperationEntity, }; export type RemoveDMOperationsOperation = { @@ -29,11 +38,33 @@ +type: 'remove_all_dm_operations', }; +// Queued DM Operations ops +export type AddQueuedDMOperationOperation = { + +type: 'add_queued_dm_operation', + +payload: QueueDMOpsPayload, +}; + +export type ClearDMOperationsQueueOperation = { + +type: 'clear_dm_operations_queue', + +payload: { + +condition: QueueDMOpsCondition, + }, +}; + +export type PruneQueuedDMOperationsOperation = { + +type: 'prune_queued_dm_operations', + +payload: PruneDMOpsQueuePayload, +}; + export type DMOperationStoreOperation = | ReplaceDMOperationOperation | RemoveDMOperationsOperation - | RemoveAllDMOperationsOperation; + | RemoveAllDMOperationsOperation + | AddQueuedDMOperationOperation + | ClearDMOperationsQueueOperation + | PruneQueuedDMOperationsOperation; +// Shimmed DM Operations DB ops export type ClientDBDMOperation = { +id: string, +type: string, @@ -45,16 +76,52 @@ +payload: ClientDBDMOperation, }; +// Queued DM Operations DB ops +export type ClientDBQueuedDMOperation = { + +queueType: string, + +queueKey: string, + +operationData: string, + +timestamp: string, +}; + +export type ClientDBAddQueuedDMOperationOperation = { + +type: 'add_queued_dm_operation', + +payload: { + +queueType: QueuedDMOperationCondition, + +queueKey: string, + +operationData: string, + +timestamp: string, + }, +}; + +export type ClientDBClearDMOperationsQueueOperation = { + +type: 'clear_dm_operations_queue', + +payload: { + +queueType: QueuedDMOperationCondition, + +queueKey: string, + }, +}; + +export type ClientDBPruneQueuedDMOperationsOperation = { + +type: 'prune_queued_dm_operations', + +payload: { + +timestamp: string, + }, +}; + export type ClientDBDMOperationStoreOperation = | ClientDBReplaceDMOperationOperation | RemoveDMOperationsOperation - | RemoveAllDMOperationsOperation; + | RemoveAllDMOperationsOperation + | ClientDBAddQueuedDMOperationOperation + | ClientDBClearDMOperationsQueueOperation + | ClientDBPruneQueuedDMOperationsOperation; function convertDMOperationIntoClientDBDMOperation({ id, type, operation, -}: Operation): ClientDBDMOperation { +}: DMOperationEntity): ClientDBDMOperation { return { id, type, @@ -62,29 +129,9 @@ }; } -function convertDMOperationOpsToClientDBOps( - ops: ?$ReadOnlyArray, -): $ReadOnlyArray { - if (!ops) { - return []; - } - return ops.map(operation => { - if ( - operation.type === 'remove_dm_operations' || - operation.type === 'remove_all_dm_operations' - ) { - return operation; - } - return { - type: 'replace_dm_operation', - payload: convertDMOperationIntoClientDBDMOperation(operation.payload), - }; - }); -} - function convertClientDBDMOperationToDMOperation( operation: ClientDBDMOperation, -): Operation { +): DMOperationEntity { return { id: operation.id, type: operation.type, @@ -206,8 +253,189 @@ }; } +function getQueueKeyFromCondition(condition: QueueDMOpsCondition): string { + if (condition.type === queuedDMOperationConditionType.THREAD) { + return condition.threadID; + } + if (condition.type === queuedDMOperationConditionType.ENTRY) { + return condition.entryID; + } + if (condition.type === queuedDMOperationConditionType.MESSAGE) { + return condition.messageID; + } + return `${condition.threadID}#${condition.userID}`; +} + +export const dmOperationsStoreOpsHandlers: BaseStoreOpsHandlers< + QueuedDMOperations, + DMOperationStoreOperation, + ClientDBDMOperationStoreOperation, + QueuedDMOperations, + ClientDBQueuedDMOperation, +> = { + processStoreOperations( + store: QueuedDMOperations, + ops: $ReadOnlyArray, + ): QueuedDMOperations { + if (ops.length === 0) { + return store; + } + + let processedStore: QueuedDMOperations = { ...store }; + + for (const op of ops) { + if (op.type === 'add_queued_dm_operation') { + const { condition, operation, timestamp } = op.payload; + processedStore = addQueuedDMOpsToStore( + processedStore, + condition, + operation, + timestamp, + ); + } else if (op.type === 'clear_dm_operations_queue') { + const { condition } = op.payload; + processedStore = removeQueuedDMOpsToStore(processedStore, condition); + } else if (op.type === 'prune_queued_dm_operations') { + const filterOperations = (queue: OperationsQueue) => + queue.filter(item => item.timestamp >= op.payload.pruneMaxTimestamp); + processedStore = { + ...processedStore, + threadQueue: _mapValues(operations => filterOperations(operations))( + store.threadQueue, + ), + messageQueue: _mapValues(operations => filterOperations(operations))( + store.messageQueue, + ), + entryQueue: _mapValues(operations => filterOperations(operations))( + store.entryQueue, + ), + membershipQueue: _mapValues(threadMembershipQueue => + _mapValues(operations => filterOperations(operations))( + threadMembershipQueue, + ), + )(store.membershipQueue), + }; + } else if (op.type === 'remove_dm_operations') { + processedStore = { + ...processedStore, + shimmedOperations: store.shimmedOperations.filter( + item => !op.payload.ids.includes(item.id), + ), + }; + } + } + + return processedStore; + }, + + convertOpsToClientDBOps( + ops: ?$ReadOnlyArray, + ): $ReadOnlyArray { + if (!ops) { + return []; + } + + return ops.map(operation => { + if ( + operation.type === 'remove_dm_operations' || + operation.type === 'remove_all_dm_operations' + ) { + return operation; + } else if (operation.type === 'replace_dm_operation') { + return { + type: 'replace_dm_operation', + payload: convertDMOperationIntoClientDBDMOperation(operation.payload), + }; + } else if (operation.type === 'add_queued_dm_operation') { + const { + operation: dmOperation, + timestamp, + condition, + } = operation.payload; + + return { + type: 'add_queued_dm_operation', + payload: { + queueType: condition.type, + queueKey: getQueueKeyFromCondition(condition), + operationData: JSON.stringify(dmOperation), + timestamp: timestamp.toString(), + }, + }; + } else if (operation.type === 'prune_queued_dm_operations') { + return { + type: 'prune_queued_dm_operations', + payload: { + timestamp: operation.payload.pruneMaxTimestamp.toString(), + }, + }; + } else { + const { condition } = operation.payload; + + return { + type: 'clear_dm_operations_queue', + payload: { + queueType: condition.type, + queueKey: getQueueKeyFromCondition(condition), + }, + }; + } + }); + }, + + translateClientDBData( + data: $ReadOnlyArray, + ): QueuedDMOperations { + let processedStore: QueuedDMOperations = { + threadQueue: {}, + messageQueue: {}, + entryQueue: {}, + membershipQueue: {}, + shimmedOperations: [], + }; + + data.forEach((item: ClientDBQueuedDMOperation) => { + const { queueType, queueKey, operationData, timestamp } = item; + const conditionType = assertQueuedDMOperationCondition(queueType); + let condition: QueueDMOpsCondition; + + if (conditionType === queuedDMOperationConditionType.THREAD) { + condition = { + type: queuedDMOperationConditionType.THREAD, + threadID: queueKey, + }; + } else if (conditionType === queuedDMOperationConditionType.ENTRY) { + condition = { + type: queuedDMOperationConditionType.ENTRY, + entryID: queueKey, + }; + } else if (conditionType === queuedDMOperationConditionType.MESSAGE) { + condition = { + type: queuedDMOperationConditionType.MESSAGE, + messageID: queueKey, + }; + } else { + const [threadID, userID] = queueKey.split('#'); + condition = { + type: queuedDMOperationConditionType.MEMBERSHIP, + threadID, + userID, + }; + } + + processedStore = addQueuedDMOpsToStore( + processedStore, + condition, + JSON.parse(operationData), + parseInt(timestamp, 10), + ); + }); + + return processedStore; + }, +}; + export { convertDMOperationIntoClientDBDMOperation, - convertDMOperationOpsToClientDBOps, convertClientDBDMOperationToDMOperation, }; diff --git a/lib/reducers/message-reducer.test.js b/lib/reducers/message-reducer.test.js --- a/lib/reducers/message-reducer.test.js +++ b/lib/reducers/message-reducer.test.js @@ -299,7 +299,9 @@ threadActivityStore: {}, entries: {}, messageStoreLocalMessageInfos: {}, + dmOperations: [], holders: {}, + queuedDMOperations: null, }, }, { diff --git a/lib/shared/redux/client-db-utils.js b/lib/shared/redux/client-db-utils.js --- a/lib/shared/redux/client-db-utils.js +++ b/lib/shared/redux/client-db-utils.js @@ -5,7 +5,7 @@ import { auxUserStoreOpsHandlers } from '../../ops/aux-user-store-ops.js'; import { communityStoreOpsHandlers } from '../../ops/community-store-ops.js'; import { createReplaceThreadOperation } from '../../ops/create-replace-thread-operation.js'; -import { convertDMOperationOpsToClientDBOps } from '../../ops/dm-operations-store-ops.js'; +import { dmOperationsStoreOpsHandlers } from '../../ops/dm-operations-store-ops.js'; import { entryStoreOpsHandlers } from '../../ops/entries-store-ops.js'; import { holderStoreOpsHandlers } from '../../ops/holder-store-ops.js'; import { integrityStoreOpsHandlers } from '../../ops/integrity-store-ops.js'; @@ -104,7 +104,9 @@ const convertedEntryStoreOperations = entryStoreOpsHandlers.convertOpsToClientDBOps(entryStoreOperations); const convertedDMOperationStoreOperations = - convertDMOperationOpsToClientDBOps(dmOperationStoreOperations); + dmOperationsStoreOpsHandlers.convertOpsToClientDBOps( + dmOperationStoreOperations, + ); const convertedHolderStoreOperations = holderStoreOpsHandlers.convertOpsToClientDBOps(holderStoreOperations); diff --git a/lib/types/store-ops-types.js b/lib/types/store-ops-types.js --- a/lib/types/store-ops-types.js +++ b/lib/types/store-ops-types.js @@ -2,6 +2,7 @@ import type { AuxUserInfos } from './aux-user-types.js'; import type { CommunityInfos } from './community-types.js'; +import type { QueuedDMOperations } from './dm-ops.js'; import type { DraftStoreOperation, ClientDBDraftStoreOperation, @@ -41,6 +42,8 @@ ClientDBDMOperation, ClientDBDMOperationStoreOperation, DMOperationStoreOperation, + ClientDBQueuedDMOperation, + DMOperationEntity, } from '../ops/dm-operations-store-ops.js'; import type { ClientDBEntryInfo, @@ -144,6 +147,7 @@ +messageStoreLocalMessageInfos: $ReadOnlyArray, +dmOperations: $ReadOnlyArray, +holders: $ReadOnlyArray, + +queuedDMOperations: $ReadOnlyArray, }; export type ClientStore = { @@ -162,5 +166,7 @@ +threadActivityStore: ?ThreadActivityStore, +entries: ?RawEntryInfos, +messageStoreLocalMessageInfos: ?MessageStoreLocalMessageInfos, + +dmOperations: ?$ReadOnlyArray, +holders: ?StoredHolders, + +queuedDMOperations: ?QueuedDMOperations, }; diff --git a/native/database/store.js b/native/database/store.js --- a/native/database/store.js +++ b/native/database/store.js @@ -2,6 +2,10 @@ import { auxUserStoreOpsHandlers } from 'lib/ops/aux-user-store-ops.js'; import { communityStoreOpsHandlers } from 'lib/ops/community-store-ops.js'; +import { + convertClientDBDMOperationToDMOperation, + dmOperationsStoreOpsHandlers, +} from 'lib/ops/dm-operations-store-ops.js'; import { entryStoreOpsHandlers } from 'lib/ops/entries-store-ops.js'; import { holderStoreOpsHandlers } from 'lib/ops/holder-store-ops.js'; import { integrityStoreOpsHandlers } from 'lib/ops/integrity-store-ops.js'; @@ -36,7 +40,9 @@ threadActivityEntries, entries, messageStoreLocalMessageInfos, + dmOperations, holders, + queuedDMOperations, } = await commCoreModule.getClientDBStore(dbID); const threadInfosFromDB = threadStoreOpsHandlers.translateClientDBData(threads); @@ -59,7 +65,12 @@ const localMessageInfosFromDB = translateClientDBLocalMessageInfos( messageStoreLocalMessageInfos, ); + const dmOperationsFromDB = dmOperations.map( + convertClientDBDMOperationToDMOperation, + ); const holdersFromDB = holderStoreOpsHandlers.translateClientDBData(holders); + const queuedDMOperationsFromDB = + dmOperationsStoreOpsHandlers.translateClientDBData(queuedDMOperations); return { drafts, @@ -77,7 +88,9 @@ threadActivityStore: threadActivityStoreFromDB, entries: entriesFromDB, messageStoreLocalMessageInfos: localMessageInfosFromDB, + dmOperations: dmOperationsFromDB, holders: holdersFromDB, + queuedDMOperations: queuedDMOperationsFromDB, }; } diff --git a/web/database/store.js b/web/database/store.js --- a/web/database/store.js +++ b/web/database/store.js @@ -2,6 +2,10 @@ import { auxUserStoreOpsHandlers } from 'lib/ops/aux-user-store-ops.js'; import { communityStoreOpsHandlers } from 'lib/ops/community-store-ops.js'; +import { + convertClientDBDMOperationToDMOperation, + dmOperationsStoreOpsHandlers, +} from 'lib/ops/dm-operations-store-ops.js'; import { entryStoreOpsHandlers } from 'lib/ops/entries-store-ops.js'; import { holderStoreOpsHandlers } from 'lib/ops/holder-store-ops.js'; import { integrityStoreOpsHandlers } from 'lib/ops/integrity-store-ops.js'; @@ -40,7 +44,9 @@ threadActivityStore: null, entries: null, messageStoreLocalMessageInfos: null, + dmOperations: null, holders: null, + queuedDMOperations: null, }; const data = await sharedWorker.schedule({ type: workerRequestMessageTypes.GET_CLIENT_STORE, @@ -160,6 +166,15 @@ }; } + if (data?.store?.dmOperations && data.store.dmOperations.length > 0) { + result = { + ...result, + dmOperations: data.store.dmOperations.map( + convertClientDBDMOperationToDMOperation, + ), + }; + } + if (data?.store?.holders && data.store.holders.length > 0) { result = { ...result, @@ -167,6 +182,18 @@ }; } + if ( + data?.store?.queuedDMOperations && + data.store.queuedDMOperations.length > 0 + ) { + result = { + ...result, + queuedDMOperations: dmOperationsStoreOpsHandlers.translateClientDBData( + data.store.queuedDMOperations, + ), + }; + } + return result; } diff --git a/web/shared-worker/queries/queued-dm-operations-queries.test.js b/web/shared-worker/queries/queued-dm-operations-queries.test.js --- a/web/shared-worker/queries/queued-dm-operations-queries.test.js +++ b/web/shared-worker/queries/queued-dm-operations-queries.test.js @@ -1,11 +1,10 @@ // @flow +import type { ClientDBQueuedDMOperation } from 'lib/ops/dm-operations-store-ops.js'; + import { getDatabaseModule } from '../db-module.js'; import type { EmscriptenModule } from '../types/module.js'; -import { - type SQLiteQueryExecutor, - type ClientDBQueuedDMOperation, -} from '../types/sqlite-query-executor.js'; +import { type SQLiteQueryExecutor } from '../types/sqlite-query-executor.js'; import { clearSensitiveData } from '../utils/db-utils.js'; const FILE_PATH = 'test.sqlite'; diff --git a/web/shared-worker/types/sqlite-query-executor.js b/web/shared-worker/types/sqlite-query-executor.js --- a/web/shared-worker/types/sqlite-query-executor.js +++ b/web/shared-worker/types/sqlite-query-executor.js @@ -2,7 +2,10 @@ import type { ClientDBAuxUserInfo } from 'lib/ops/aux-user-store-ops.js'; import type { ClientDBCommunityInfo } from 'lib/ops/community-store-ops.js'; -import type { ClientDBDMOperation } from 'lib/ops/dm-operations-store-ops.js'; +import type { + ClientDBDMOperation, + ClientDBQueuedDMOperation, +} from 'lib/ops/dm-operations-store-ops.js'; import type { ClientDBEntryInfo } from 'lib/ops/entries-store-ops.js'; import type { ClientDBIntegrityThreadHash } from 'lib/ops/integrity-store-ops.js'; import type { ClientDBKeyserverInfo } from 'lib/ops/keyserver-store-ops.js'; @@ -51,13 +54,6 @@ +medias: $ReadOnlyArray, }; -export type ClientDBQueuedDMOperation = { - +queueType: string, - +queueKey: string, - +operationData: string, - +timestamp: string, -}; - declare export class SQLiteQueryExecutor { constructor(sqliteFilePath: string, skipMigration: boolean): void; diff --git a/web/shared-worker/worker/process-operations.js b/web/shared-worker/worker/process-operations.js --- a/web/shared-worker/worker/process-operations.js +++ b/web/shared-worker/worker/process-operations.js @@ -742,6 +742,7 @@ sqliteQueryExecutor.getAllMessageStoreLocalMessageInfos(), dmOperations: sqliteQueryExecutor.getAllDMOperations(), holders: sqliteQueryExecutor.getHolders(), + queuedDMOperations: sqliteQueryExecutor.getQueuedDMOperations(), }; }