Page MenuHomePhabricator

D9265.diff
No OneTemporary

D9265.diff

diff --git a/lib/actions/integrity-actions.js b/lib/actions/integrity-actions.js
new file mode 100644
--- /dev/null
+++ b/lib/actions/integrity-actions.js
@@ -0,0 +1,5 @@
+// @flow
+
+const updateIntegrityStoreActionType = 'UPDATE_INTEGRITY_STORE';
+
+export { updateIntegrityStoreActionType };
diff --git a/lib/components/integrity-handler.react.js b/lib/components/integrity-handler.react.js
new file mode 100644
--- /dev/null
+++ b/lib/components/integrity-handler.react.js
@@ -0,0 +1,63 @@
+// @flow
+
+import * as React from 'react';
+import { useDispatch } from 'react-redux';
+
+import { updateIntegrityStoreActionType } from '../actions/integrity-actions.js';
+import { splitIntoChunks } from '../utils/array.js';
+import { useSelector } from '../utils/redux-utils.js';
+
+const BATCH_SIZE = 50;
+// Time between hashing of each thread batch
+const BATCH_INTERVAL = 500; // in milliseconds
+
+function IntegrityHandler(): null {
+ const dispatch = useDispatch();
+
+ const threadInfos = useSelector(state => state.threadStore.threadInfos);
+ const integrityStore = useSelector(state => state.integrityStore);
+
+ const [batches, setBatches] = React.useState(null);
+ const timeout = React.useRef(null);
+
+ React.useEffect(() => {
+ if (integrityStore.threadHashingStatus === 'starting') {
+ const threadIDs = Object.keys(threadInfos);
+ setBatches(splitIntoChunks(threadIDs, BATCH_SIZE));
+ dispatch({
+ type: updateIntegrityStoreActionType,
+ payload: { threadHashingStatus: 'running' },
+ });
+ } else if (integrityStore.threadHashingStatus === 'completed') {
+ clearTimeout(timeout.current);
+ setBatches(null);
+ }
+ }, [dispatch, integrityStore.threadHashingStatus, threadInfos]);
+
+ React.useEffect(() => {
+ if (!batches) {
+ return undefined;
+ }
+ const [batch, ...rest] = batches;
+ if (!batch) {
+ dispatch({
+ type: updateIntegrityStoreActionType,
+ payload: { threadHashingStatus: 'completed' },
+ });
+ return undefined;
+ }
+
+ dispatch({
+ type: updateIntegrityStoreActionType,
+ payload: { threadIDsToHash: batch },
+ });
+
+ const timeoutID = setTimeout(() => setBatches(rest), BATCH_INTERVAL);
+ timeout.current = timeoutID;
+ return () => clearTimeout(timeoutID);
+ }, [batches, dispatch]);
+
+ return null;
+}
+
+export default IntegrityHandler;
diff --git a/lib/reducers/integrity-reducer.js b/lib/reducers/integrity-reducer.js
new file mode 100644
--- /dev/null
+++ b/lib/reducers/integrity-reducer.js
@@ -0,0 +1,85 @@
+// @flow
+
+import { setClientDBStoreActionType } from '../actions/client-db-store-actions.js';
+import { updateIntegrityStoreActionType } from '../actions/integrity-actions.js';
+import { siweAuthActionTypes } from '../actions/siwe-actions.js';
+import {
+ logInActionTypes,
+ registerActionTypes,
+} from '../actions/user-actions.js';
+import type { ThreadStoreOperation } from '../ops/thread-store-ops';
+import type { IntegrityStore } from '../types/integrity-types';
+import type { BaseAction } from '../types/redux-types.js';
+import { fullStateSyncActionType } from '../types/socket-types.js';
+import type { RawThreadInfo } from '../types/thread-types.js';
+import { hash } from '../utils/objects.js';
+
+function reduceIntegrityStore(
+ state: IntegrityStore,
+ action: BaseAction,
+ threadInfos: { +[string]: RawThreadInfo },
+ threadStoreOperations: $ReadOnlyArray<ThreadStoreOperation>,
+): IntegrityStore {
+ if (
+ action.type === logInActionTypes.success ||
+ action.type === siweAuthActionTypes.success ||
+ action.type === registerActionTypes.success ||
+ action.type === fullStateSyncActionType ||
+ (action.type === setClientDBStoreActionType &&
+ !!action.payload.threadStore &&
+ state.threadHashingStatus !== 'completed')
+ ) {
+ return { threadHashes: {}, threadHashingStatus: 'starting' };
+ }
+ let newState = state;
+ if (action.type === updateIntegrityStoreActionType) {
+ if (action.payload.threadIDsToHash) {
+ const newThreadHashes = Object.fromEntries(
+ action.payload.threadIDsToHash
+ .map(id => [id, threadInfos[id]])
+ .filter(([, info]) => !!info)
+ .map(([id, info]) => [id, hash(info)]),
+ );
+
+ newState = {
+ ...newState,
+ threadHashes: {
+ ...newState.threadHashes,
+ ...newThreadHashes,
+ },
+ };
+ }
+ if (action.payload.threadHashingStatus) {
+ newState = {
+ ...newState,
+ threadHashingStatus: action.payload.threadHashingStatus,
+ };
+ }
+ }
+ if (threadStoreOperations.length === 0) {
+ return newState;
+ }
+ let processedThreadHashes = { ...newState.threadHashes };
+ let threadHashingStatus = newState.threadHashingStatus;
+ for (const operation of threadStoreOperations) {
+ if (operation.type === 'replace') {
+ processedThreadHashes[operation.payload.id] = hash(
+ operation.payload.threadInfo,
+ );
+ } else if (operation.type === 'remove') {
+ for (const id of operation.payload.ids) {
+ delete processedThreadHashes[id];
+ }
+ } else if (operation.type === 'remove_all') {
+ processedThreadHashes = {};
+ threadHashingStatus = 'completed';
+ }
+ }
+ return {
+ ...newState,
+ threadHashes: processedThreadHashes,
+ threadHashingStatus,
+ };
+}
+
+export { reduceIntegrityStore };
diff --git a/lib/reducers/master-reducer.js b/lib/reducers/master-reducer.js
--- a/lib/reducers/master-reducer.js
+++ b/lib/reducers/master-reducer.js
@@ -7,6 +7,7 @@
import { reduceDraftStore } from './draft-reducer.js';
import reduceEnabledApps from './enabled-apps-reducer.js';
import { reduceEntryInfos } from './entry-reducer.js';
+import { reduceIntegrityStore } from './integrity-reducer.js';
import reduceInviteLinks from './invite-links-reducer.js';
import reduceKeyserverStore from './keyserver-reducer.js';
import reduceLifecycleState from './lifecycle-state-reducer.js';
@@ -150,6 +151,12 @@
state.threadActivityStore,
action,
),
+ integrityStore: reduceIntegrityStore(
+ state.integrityStore,
+ action,
+ threadStore.threadInfos,
+ threadStoreOperations,
+ ),
},
storeOperations: {
draftStoreOperations,
diff --git a/lib/types/integrity-types.js b/lib/types/integrity-types.js
new file mode 100644
--- /dev/null
+++ b/lib/types/integrity-types.js
@@ -0,0 +1,10 @@
+// @flow
+
+export type IntegrityStore = {
+ +threadHashes: { +[string]: number },
+ +threadHashingStatus:
+ | 'data_not_loaded'
+ | 'starting'
+ | 'running'
+ | 'completed',
+};
diff --git a/lib/types/redux-types.js b/lib/types/redux-types.js
--- a/lib/types/redux-types.js
+++ b/lib/types/redux-types.js
@@ -38,6 +38,7 @@
CalendarThreadFilter,
SetCalendarDeletedFilterPayload,
} from './filter-types.js';
+import type { IntegrityStore } from './integrity-types.js';
import type { KeyserverStore } from './keyserver-types.js';
import type { LifecycleState } from './lifecycle-state-types.js';
import type {
@@ -139,6 +140,7 @@
+inviteLinksStore: InviteLinksStore,
+keyserverStore: KeyserverStore,
+threadActivityStore: ThreadActivityStore,
+ +integrityStore: IntegrityStore,
...
};
@@ -1214,6 +1216,13 @@
| {
+type: 'UPDATE_THREAD_LAST_NAVIGATED',
+payload: { +threadID: string, +time: number },
+ }
+ | {
+ +type: 'UPDATE_INTEGRITY_STORE',
+ +payload: {
+ +threadIDsToHash?: $ReadOnlyArray<string>,
+ +threadHashingStatus?: 'starting' | 'running' | 'completed',
+ },
};
export type ActionPayload = ?(Object | Array<*> | $ReadOnlyArray<*> | string);
diff --git a/lib/utils/array.js b/lib/utils/array.js
--- a/lib/utils/array.js
+++ b/lib/utils/array.js
@@ -23,4 +23,19 @@
}
}
-export { getAllTuples, cartesianProduct, pushAll };
+function splitIntoChunks<T>(
+ array: $ReadOnlyArray<T>,
+ batchSize: number,
+): Array<Array<T>> {
+ const chunks = [];
+
+ let i = 0;
+ while (i < array.length) {
+ chunks.push(array.slice(i, i + batchSize));
+ i += batchSize;
+ }
+
+ return chunks;
+}
+
+export { getAllTuples, cartesianProduct, pushAll, splitIntoChunks };
diff --git a/native/redux/default-state.js b/native/redux/default-state.js
--- a/native/redux/default-state.js
+++ b/native/redux/default-state.js
@@ -86,6 +86,7 @@
isBackupEnabled: false,
},
threadActivityStore: {},
+ integrityStore: { threadHashes: {}, threadHashingStatus: 'starting' },
}: AppState);
export { defaultState };
diff --git a/native/redux/persist.js b/native/redux/persist.js
--- a/native/redux/persist.js
+++ b/native/redux/persist.js
@@ -776,6 +776,13 @@
},
};
},
+ [52]: async state => ({
+ ...state,
+ integrityStore: {
+ threadHashes: {},
+ threadHashingStatus: 'data_not_loaded',
+ },
+ }),
};
// After migration 31, we'll no longer want to persist `messageStore.messages`
@@ -907,7 +914,7 @@
'connection',
],
debug: __DEV__,
- version: 51,
+ version: 52,
transforms: [
messageStoreMessagesBlocklistTransform,
reportStoreTransform,
diff --git a/native/redux/state-types.js b/native/redux/state-types.js
--- a/native/redux/state-types.js
+++ b/native/redux/state-types.js
@@ -7,6 +7,7 @@
import type { EnabledApps } from 'lib/types/enabled-apps.js';
import type { EntryStore, CalendarQuery } from 'lib/types/entry-types.js';
import type { CalendarFilter } from 'lib/types/filter-types.js';
+import type { IntegrityStore } from 'lib/types/integrity-types.js';
import type { KeyserverStore } from 'lib/types/keyserver-types.js';
import type { LifecycleState } from 'lib/types/lifecycle-state-types.js';
import type { InviteLinksStore } from 'lib/types/link-types.js';
@@ -60,4 +61,5 @@
+keyserverStore: KeyserverStore,
+threadActivityStore: ThreadActivityStore,
+localSettings: LocalSettings,
+ +integrityStore: IntegrityStore,
};
diff --git a/native/root.react.js b/native/root.react.js
--- a/native/root.react.js
+++ b/native/root.react.js
@@ -21,6 +21,7 @@
import { EditUserAvatarProvider } from 'lib/components/edit-user-avatar-provider.react.js';
import { ENSCacheProvider } from 'lib/components/ens-cache-provider.react.js';
+import IntegrityHandler from 'lib/components/integrity-handler.react.js';
import { MediaCacheProvider } from 'lib/components/media-cache-provider.react.js';
import { actionLogger } from 'lib/utils/action-logger.js';
@@ -243,6 +244,7 @@
<ThemeHandler />
<OrientationHandler />
<BackupHandler />
+ <IntegrityHandler />
</>
);
let navigation;
diff --git a/web/redux/default-state.js b/web/redux/default-state.js
--- a/web/redux/default-state.js
+++ b/web/redux/default-state.js
@@ -89,6 +89,7 @@
},
threadActivityStore: {},
initialStateLoaded: false,
+ integrityStore: { threadHashes: {}, threadHashingStatus: 'starting' },
});
export { defaultWebState };
diff --git a/web/redux/persist.js b/web/redux/persist.js
--- a/web/redux/persist.js
+++ b/web/redux/persist.js
@@ -130,6 +130,10 @@
return state;
},
+ [6]: async state => ({
+ ...state,
+ integrityStore: { threadHashes: {}, threadHashingStatus: 'starting' },
+ }),
};
const persistWhitelist = [
@@ -242,7 +246,7 @@
{ debug: isDev },
migrateStorageToSQLite,
): any),
- version: 5,
+ version: 6,
transforms: [keyserverStoreTransform],
};
diff --git a/web/redux/redux-setup.js b/web/redux/redux-setup.js
--- a/web/redux/redux-setup.js
+++ b/web/redux/redux-setup.js
@@ -22,6 +22,7 @@
import type { EnabledApps } from 'lib/types/enabled-apps.js';
import type { EntryStore, CalendarQuery } from 'lib/types/entry-types.js';
import { type CalendarFilter } from 'lib/types/filter-types.js';
+import type { IntegrityStore } from 'lib/types/integrity-types.js';
import type { KeyserverStore } from 'lib/types/keyserver-types.js';
import type { LifecycleState } from 'lib/types/lifecycle-state-types.js';
import type { InviteLinksStore } from 'lib/types/link-types.js';
@@ -100,6 +101,7 @@
+keyserverStore: KeyserverStore,
+threadActivityStore: ThreadActivityStore,
+initialStateLoaded: boolean,
+ +integrityStore: IntegrityStore,
};
export type Action =
diff --git a/web/root.js b/web/root.js
--- a/web/root.js
+++ b/web/root.js
@@ -9,6 +9,7 @@
import { persistReducer, persistStore } from 'redux-persist';
import thunk from 'redux-thunk';
+import IntegrityHandler from 'lib/components/integrity-handler.react.js';
import { reduxLoggerMiddleware } from 'lib/utils/action-logger.js';
import App from './app.react.js';
@@ -41,6 +42,7 @@
</Router>
<Socket />
<SQLiteDataHandler />
+ <IntegrityHandler />
</InitialReduxStateGate>
</ErrorBoundary>
</Provider>

File Metadata

Mime Type
text/plain
Expires
Sat, Oct 5, 5:26 PM (22 h, 26 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2245126
Default Alt Text
D9265.diff (12 KB)

Event Timeline