From 859f58862624187dce4fe8be5e0cf9c0523cb5f1 Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Mon, 30 Dec 2024 14:51:48 -0800 Subject: [PATCH 1/6] Remove unused cache functions, implement deleteCachedRecordsFromIds --- app/src/utils/record-cache/index.ts | 10 -- app/src/utils/record-cache/sqlite-cache.ts | 103 +-------------------- 2 files changed, 1 insertion(+), 112 deletions(-) diff --git a/app/src/utils/record-cache/index.ts b/app/src/utils/record-cache/index.ts index de7632133..4896c0396 100644 --- a/app/src/utils/record-cache/index.ts +++ b/app/src/utils/record-cache/index.ts @@ -16,10 +16,6 @@ export interface RecordCacheDownloadRequestSpec { idsToCache: string[]; } -export interface RecordCacheAddSpec { - setId: string; - idsToCache: string[]; -} /** * @desc Cached Metadata for Recordsets * @property { string } setID Recordset ID @@ -62,12 +58,6 @@ abstract class RecordCacheService { abstract loadActivity(id: string): Promise; abstract loadIapp(id: string, type: IappRecordMode): Promise; - abstract addCachedSet(spec: RecordCacheAddSpec): Promise; - - abstract listCachedSets(): Promise; - - abstract deleteCachedSet(id: string): Promise; - abstract fetchPaginatedCachedIappRecords( recordSetIdList: string[], page: number, diff --git a/app/src/utils/record-cache/sqlite-cache.ts b/app/src/utils/record-cache/sqlite-cache.ts index 9096fad0d..f65a9d14b 100644 --- a/app/src/utils/record-cache/sqlite-cache.ts +++ b/app/src/utils/record-cache/sqlite-cache.ts @@ -5,13 +5,7 @@ import IappRecord from 'interfaces/IappRecord'; import IappTableRow from 'interfaces/IappTableRecord'; import UserRecord from 'interfaces/UserRecord'; import { GeoJSONSourceSpecification } from 'maplibre-gl'; -import { - IappRecordMode, - RecordCacheAddSpec, - RecordCacheService, - RecordSetCacheMetadata, - RecordSetSourceMetadata -} from 'utils/record-cache/index'; +import { IappRecordMode, RecordCacheService, RecordSetSourceMetadata } from 'utils/record-cache/index'; import { sqlite } from 'utils/sharedSQLiteInstance'; const CACHE_DB_NAME = 'record_cache.db'; @@ -68,74 +62,6 @@ class SQLiteRecordCacheService extends RecordCacheService { return SQLiteRecordCacheService._instance; } - async addCachedSet(spec: RecordCacheAddSpec): Promise { - if (this.cacheDB == null) { - throw new Error(CACHE_UNAVAILABLE); - } - try { - await this.cacheDB.beginTransaction(); - - await this.cacheDB.query( - //language=SQLite - ` - INSERT INTO (SET_ID) - VALUES (?)`, - [spec.setId] - ); - for (const s of spec.idsToCache) { - await this.cacheDB.query( - //language=SQLite - ` - INSERT INTO CACHED_RECORD_TO_CACHE_METADATA (CACHE_METADATA_ID, RECORD_ID) - VALUES (?, ?)`, - [spec.setId, s] - ); - } - await this.cacheDB.commitTransaction(); - } catch (e) { - await this.cacheDB.rollbackTransaction(); - } - } - - async deleteCachedSet(id: string): Promise { - if (this.cacheDB == null) { - throw new Error(CACHE_UNAVAILABLE); - } - try { - await this.cacheDB.beginTransaction(); - - // delete the record of this set - await this.cacheDB.query( - //language=SQLite - `DELETE - FROM CACHED_RECORD_TO_CACHE_METADATA - WHERE CACHE_METADATA_ID = ?`, - [id] - ); - - // delete the associations - await this.cacheDB.query( - //language=SQLite - `DELETE - FROM CACHE_METADATA - WHERE SET_ID = ?`, - [id] - ); - - // delete and records that are now unreferenced - // won't delete records that are still referenced by another cache set (in case a record exists in more than one) - await this.cacheDB.query( - //language=SQLite - `DELETE - FROM CACHED_RECORDS - WHERE ID NOT IN (SELECT RECORD_ID FROM CACHED_RECORD_TO_CACHE_METADATA)` - ); - - await this.cacheDB.commitTransaction(); - } catch (e) { - await this.cacheDB.rollbackTransaction(); - } - } /** * @desc fetch `n` records for a given recordset, supporting pagination * @param recordSetID Recordset to filter from @@ -205,33 +131,6 @@ class SQLiteRecordCacheService extends RecordCacheService { .filter((record) => record !== null); return response; } - async listCachedSets(): Promise { - if (this.cacheDB == null) { - throw new Error(CACHE_UNAVAILABLE); - } - - try { - await this.cacheDB.beginTransaction(); - - const rows = await this.cacheDB.query(`SELECT SET_ID - FROM CACHE_METADATA`); - - const cachedSets: RecordSetCacheMetadata[] = []; - - if (rows.values) { - for (const row of rows.values) { - cachedSets.push({ setId: row['SET_ID'] }); - } - } - - await this.cacheDB.commitTransaction(); - - return cachedSets; - } catch (e) { - await this.cacheDB.rollbackTransaction(); - throw new Error('error while querying cache'); - } - } async loadActivity(id: string): Promise { if (this.cacheDB == null) { From b48888c033f400848f479a26311c08b8b28f959c Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Mon, 30 Dec 2024 14:54:10 -0800 Subject: [PATCH 2/6] rewrite deleteCache to use frequencyMap to determine delete Ids, don't wipe metadata in pending state --- app/src/state/actions/cache/RecordCache.ts | 27 ++++++++++++++++++---- app/src/state/reducers/userSettings.ts | 10 ++------ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/app/src/state/actions/cache/RecordCache.ts b/app/src/state/actions/cache/RecordCache.ts index 2152a31da..3c657a0a6 100644 --- a/app/src/state/actions/cache/RecordCache.ts +++ b/app/src/state/actions/cache/RecordCache.ts @@ -7,11 +7,28 @@ import { RecordCacheServiceFactory } from 'utils/record-cache/context'; class RecordCache { static readonly PREFIX = 'RecordCache'; - static readonly deleteCache = createAsyncThunk(`${this.PREFIX}/deleteCache`, async (spec: { setId: string }) => { - const service = await RecordCacheServiceFactory.getPlatformInstance(); - - await service.deleteCachedSet(spec.setId); - }); + /** + * @desc Deletes cached records for a recordset. + * determines duplicates with a frequency map to avoid duplicating records contained elsewhere + */ + static readonly deleteCache = createAsyncThunk( + `${this.PREFIX}/deleteCache`, + async (spec: { setId: string }, { getState }) => { + const service = await RecordCacheServiceFactory.getPlatformInstance(); + const state = getState() as RootState; + const { recordSets } = state.UserSettings; + const deleteList = recordSets[spec.setId].cacheMetadata.idList ?? []; + const ids: Record = {}; + Object.keys(recordSets) + .flatMap((key) => recordSets[key].cacheMetadata.idList ?? []) + .forEach((id) => { + ids[id] ??= 0; + ids[id]++; + }); + const recordsToErase = deleteList.filter((id) => ids[id] === 1); + await service.deleteCachedRecordsFromIds(recordsToErase, recordSets[spec.setId].recordSetType); + } + ); static readonly requestCaching = createAsyncThunk( `${this.PREFIX}/requestCaching`, diff --git a/app/src/state/reducers/userSettings.ts b/app/src/state/reducers/userSettings.ts index 72f9fee5b..3da1423e7 100644 --- a/app/src/state/reducers/userSettings.ts +++ b/app/src/state/reducers/userSettings.ts @@ -200,7 +200,7 @@ function createUserSettingsReducer(configuration: AppConfig): (UserSettingsState draftState.recordSets[action.meta.arg.setId].cacheMetadata = { status: UserRecordCacheStatus.DOWNLOADING }; - } else if (RecordCache.requestCaching.rejected.match(action)) { + } else if (RecordCache.requestCaching.rejected.match(action) || RecordCache.deleteCache.rejected.match(action)) { draftState.recordSets[action.meta.arg.setId].cacheMetadata = { status: UserRecordCacheStatus.ERROR }; @@ -213,13 +213,7 @@ function createUserSettingsReducer(configuration: AppConfig): (UserSettingsState cachedCentroid: action.payload.cachedCentroid }; } else if (RecordCache.deleteCache.pending.match(action)) { - draftState.recordSets[action.meta.arg.setId].cacheMetadata = { - status: UserRecordCacheStatus.DELETING - }; - } else if (RecordCache.deleteCache.rejected.match(action)) { - draftState.recordSets[action.meta.arg.setId].cacheMetadata = { - status: UserRecordCacheStatus.ERROR - }; + draftState.recordSets[action.meta.arg.setId].cacheMetadata.status = UserRecordCacheStatus.DELETING; } else if (RecordCache.deleteCache.fulfilled.match(action)) { draftState.recordSets[action.meta.arg.setId].cacheMetadata = { status: UserRecordCacheStatus.NOT_CACHED From 7e1e340512e26717500799d22059834d5aee45b8 Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Mon, 30 Dec 2024 14:54:30 -0800 Subject: [PATCH 3/6] implement deleteCachedRecordsFromIds --- .../utils/record-cache/localforage-cache.ts | 86 +------------------ 1 file changed, 4 insertions(+), 82 deletions(-) diff --git a/app/src/utils/record-cache/localforage-cache.ts b/app/src/utils/record-cache/localforage-cache.ts index ad72ecce6..c32c5f7b4 100644 --- a/app/src/utils/record-cache/localforage-cache.ts +++ b/app/src/utils/record-cache/localforage-cache.ts @@ -1,13 +1,7 @@ import UserRecord from 'interfaces/UserRecord'; import localForage from 'localforage'; import centroid from '@turf/centroid'; -import { - IappRecordMode, - RecordCacheAddSpec, - RecordCacheService, - RecordSetCacheMetadata, - RecordSetSourceMetadata -} from 'utils/record-cache/index'; +import { IappRecordMode, RecordCacheService, RecordSetSourceMetadata } from 'utils/record-cache/index'; import { Feature } from '@turf/helpers'; import { GeoJSONSourceSpecification } from 'maplibre-gl'; import IappRecord from 'interfaces/IappRecord'; @@ -92,24 +86,6 @@ class LocalForageRecordCacheService extends RecordCacheService { return data; } - async addCachedSet(spec: RecordCacheAddSpec): Promise { - if (this.store == null) { - throw new Error('cache not available'); - } - - const cachedSets = await this.listCachedSets(); - const foundIndex = cachedSets.findIndex((p) => p.setId == spec.setId); - if (foundIndex !== -1) { - throw new Error('cached set already exists'); - } - - cachedSets.push({ - setId: spec.setId, - cachedIds: spec.idsToCache - }); - await this.store.setItem(LocalForageRecordCacheService.CACHED_SETS_METADATA_KEY, cachedSets); - } - /** * @desc fetch `n` records for a given recordset, supporting pagination * @param recordSetID Recordset to filter from @@ -131,21 +107,6 @@ class LocalForageRecordCacheService extends RecordCacheService { return results; } - async listCachedSets(): Promise { - if (this.store == null) { - return []; - } - - const metadata = (await this.store.getItem(LocalForageRecordCacheService.CACHED_SETS_METADATA_KEY)) as - | RecordSetCacheMetadata[] - | null; - if (metadata == null) { - console.error('expected key not found'); - return []; - } - return metadata; - } - /** * @desc Iterate ids to produce list of values to populate in the map. * The values only change with the recordsets, so we create the list at cache-ception to avoid querying @@ -207,51 +168,12 @@ class LocalForageRecordCacheService extends RecordCacheService { return { cachedCentroid, cachedGeoJson }; } - async deleteCachedSet(id: string): Promise { + async deleteCachedRecordsFromIds(idsToDelete: string[]): Promise { if (this.store == null) { throw new Error('cache not available'); } - - const cachedSets = await this.listCachedSets(); - const foundIndex = cachedSets.findIndex((p) => p.setId == id); - if (foundIndex !== -1) { - cachedSets.splice(foundIndex, 1); - } - try { - await this.cleanupOrphanActivities(); - } finally { - await this.store.setItem(LocalForageRecordCacheService.CACHED_SETS_METADATA_KEY, cachedSets); - } - } - - private async cleanupOrphanActivities(): Promise { - if (this.store == null) { - throw new Error('cache not available'); - } - const cachedSets = await this.listCachedSets(); - - const allKeys = await this.store.keys(); - const deletionQueue: string[] = []; - for (const k of allKeys) { - if (k == LocalForageRecordCacheService.CACHED_SETS_METADATA_KEY) { - continue; - } - let referenced = false; - for (const set of cachedSets) { - if (set.cachedIds?.includes(k)) { - referenced = true; - } - } - if (!referenced) { - deletionQueue.push(k); - } - } - for (const d of deletionQueue) { - try { - await this.store.removeItem(d); - } catch (e) { - console.error(e); - } + for (const id of idsToDelete) { + await this.store.removeItem(id.toString()); } } From fe0de66ef1d68f1ceff226091133f9cfd3adcd64 Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Tue, 31 Dec 2024 09:33:33 -0800 Subject: [PATCH 4/6] add implement deleteCachedRecordsFromIds for SQLite --- app/src/utils/record-cache/index.ts | 2 +- .../utils/record-cache/localforage-cache.ts | 3 ++- app/src/utils/record-cache/sqlite-cache.ts | 18 +++++++++++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/app/src/utils/record-cache/index.ts b/app/src/utils/record-cache/index.ts index 4896c0396..b76e2df4e 100644 --- a/app/src/utils/record-cache/index.ts +++ b/app/src/utils/record-cache/index.ts @@ -54,7 +54,7 @@ abstract class RecordCacheService { abstract saveActivity(id: string, data: unknown): Promise; abstract saveIapp(id: string, iappRecord: unknown, iappTableRow: unknown): Promise; - + abstract deleteCachedRecordsFromIds(idsToDelete: string[], recordSetType: RecordSetType): Promise; abstract loadActivity(id: string): Promise; abstract loadIapp(id: string, type: IappRecordMode): Promise; diff --git a/app/src/utils/record-cache/localforage-cache.ts b/app/src/utils/record-cache/localforage-cache.ts index c32c5f7b4..34f43be5d 100644 --- a/app/src/utils/record-cache/localforage-cache.ts +++ b/app/src/utils/record-cache/localforage-cache.ts @@ -6,6 +6,7 @@ import { Feature } from '@turf/helpers'; import { GeoJSONSourceSpecification } from 'maplibre-gl'; import IappRecord from 'interfaces/IappRecord'; import IappTableRow from 'interfaces/IappTableRecord'; +import { RecordSetType } from 'interfaces/UserRecordSet'; class LocalForageRecordCacheService extends RecordCacheService { private static _instance: LocalForageRecordCacheService; @@ -168,7 +169,7 @@ class LocalForageRecordCacheService extends RecordCacheService { return { cachedCentroid, cachedGeoJson }; } - async deleteCachedRecordsFromIds(idsToDelete: string[]): Promise { + async deleteCachedRecordsFromIds(idsToDelete: string[], recordSetType: RecordSetType): Promise { if (this.store == null) { throw new Error('cache not available'); } diff --git a/app/src/utils/record-cache/sqlite-cache.ts b/app/src/utils/record-cache/sqlite-cache.ts index f65a9d14b..49a334406 100644 --- a/app/src/utils/record-cache/sqlite-cache.ts +++ b/app/src/utils/record-cache/sqlite-cache.ts @@ -4,6 +4,7 @@ import { Feature } from '@turf/helpers'; import IappRecord from 'interfaces/IappRecord'; import IappTableRow from 'interfaces/IappTableRecord'; import UserRecord from 'interfaces/UserRecord'; +import { RecordSetType } from 'interfaces/UserRecordSet'; import { GeoJSONSourceSpecification } from 'maplibre-gl'; import { IappRecordMode, RecordCacheService, RecordSetSourceMetadata } from 'utils/record-cache/index'; import { sqlite } from 'utils/sharedSQLiteInstance'; @@ -282,7 +283,22 @@ class SQLiteRecordCacheService extends RecordCacheService { }; return { cachedCentroid, cachedGeoJson }; } - + async deleteCachedRecordsFromIds(idsToDelete: string[], recordSetType: RecordSetType): Promise { + if (this.cacheDB == null) { + throw new Error(CACHE_UNAVAILABLE); + } + const RecordsToTable = { + [RecordSetType.Activity]: 'CACHED_RECORDS', + [RecordSetType.IAPP]: 'CACHED_IAPP_RECORDS' + }; + const RECORD_TABLE = RecordsToTable[recordSetType]; + await this.cacheDB.query( + // language=SQLite + `DELETE FROM ${RECORD_TABLE} + WHERE ID IN (${idsToDelete.map(() => '?').join(', ')})`, + [...idsToDelete] + ); + } private async initializeRecordCache(sqlite: SQLiteConnection) { // Hold Migrations as named variable so we can use length to update the Db version automagically // Note: toVersion must be an integer. From a2a2750c9fa5742f4933c2ee9309d5e06a43467f Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Tue, 31 Dec 2024 10:29:16 -0800 Subject: [PATCH 5/6] Trigger cache delete on Recordset deletion --- app/src/UI/Overlay/Records/Records.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/UI/Overlay/Records/Records.tsx b/app/src/UI/Overlay/Records/Records.tsx index 7faac6971..ad8050d6a 100644 --- a/app/src/UI/Overlay/Records/Records.tsx +++ b/app/src/UI/Overlay/Records/Records.tsx @@ -5,12 +5,13 @@ import { OverlayHeader } from '../OverlayHeader'; import { useHistory } from 'react-router-dom'; import { useDispatch, useSelector } from 'utils/use_selector'; import UserSettings from 'state/actions/userSettings/UserSettings'; -import { RecordSetType } from 'interfaces/UserRecordSet'; +import { RecordSetType, UserRecordCacheStatus } from 'interfaces/UserRecordSet'; import Prompt from 'state/actions/prompts/Prompt'; import RecordSetDetails from './RecordSetDetails'; import RecordSetControl from './RecordSetControl'; import { MOBILE } from 'state/build-time-config'; import filterRecordsetsByNetworkState from 'utils/filterRecordsetsByNetworkState'; +import RecordCache from 'state/actions/cache/RecordCache'; export const Records = () => { const DEFAULT_RECORD_TYPES = ['All InvasivesBC Activities', 'All IAPP Records', 'My Drafts']; @@ -49,7 +50,10 @@ export const Records = () => { e.stopPropagation(); const callback = (userConfirmation: boolean) => { if (userConfirmation) { - dispatch(UserSettings.RecordSet.remove(set)); + if (recordSets[set]?.cacheMetadata?.status === UserRecordCacheStatus.CACHED) { + dispatch(RecordCache.deleteCache({ setId: set })); + } + setTimeout(() => dispatch(UserSettings.RecordSet.remove(set)), 250); } }; dispatch( From e3aaadbb97f3c168b12b340d475bdf96a585b269 Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:08:31 -0800 Subject: [PATCH 6/6] Convert RecordSet.remove to requestRemoval thunk. Paginate delete, make transactional --- app/src/UI/Overlay/Records/Records.tsx | 8 ++---- .../state/actions/userSettings/RecordSet.ts | 18 +++++++++++++ app/src/state/reducers/userSettings.ts | 15 +---------- app/src/utils/record-cache/sqlite-cache.ts | 25 ++++++++++++++----- 4 files changed, 40 insertions(+), 26 deletions(-) diff --git a/app/src/UI/Overlay/Records/Records.tsx b/app/src/UI/Overlay/Records/Records.tsx index ad8050d6a..243d0c8e3 100644 --- a/app/src/UI/Overlay/Records/Records.tsx +++ b/app/src/UI/Overlay/Records/Records.tsx @@ -5,13 +5,12 @@ import { OverlayHeader } from '../OverlayHeader'; import { useHistory } from 'react-router-dom'; import { useDispatch, useSelector } from 'utils/use_selector'; import UserSettings from 'state/actions/userSettings/UserSettings'; -import { RecordSetType, UserRecordCacheStatus } from 'interfaces/UserRecordSet'; +import { RecordSetType } from 'interfaces/UserRecordSet'; import Prompt from 'state/actions/prompts/Prompt'; import RecordSetDetails from './RecordSetDetails'; import RecordSetControl from './RecordSetControl'; import { MOBILE } from 'state/build-time-config'; import filterRecordsetsByNetworkState from 'utils/filterRecordsetsByNetworkState'; -import RecordCache from 'state/actions/cache/RecordCache'; export const Records = () => { const DEFAULT_RECORD_TYPES = ['All InvasivesBC Activities', 'All IAPP Records', 'My Drafts']; @@ -50,10 +49,7 @@ export const Records = () => { e.stopPropagation(); const callback = (userConfirmation: boolean) => { if (userConfirmation) { - if (recordSets[set]?.cacheMetadata?.status === UserRecordCacheStatus.CACHED) { - dispatch(RecordCache.deleteCache({ setId: set })); - } - setTimeout(() => dispatch(UserSettings.RecordSet.remove(set)), 250); + dispatch(UserSettings.RecordSet.requestRemoval({ setId: set })); } }; dispatch( diff --git a/app/src/state/actions/userSettings/RecordSet.ts b/app/src/state/actions/userSettings/RecordSet.ts index edc59cf07..5a7f3d420 100644 --- a/app/src/state/actions/userSettings/RecordSet.ts +++ b/app/src/state/actions/userSettings/RecordSet.ts @@ -1,7 +1,10 @@ import { createAction, createAsyncThunk, nanoid } from '@reduxjs/toolkit'; import { RECORD_COLOURS } from 'constants/colors'; import { RecordSetType, UserRecordCacheStatus, UserRecordSet } from 'interfaces/UserRecordSet'; +import { MOBILE } from 'state/build-time-config'; +import { RootState } from 'state/reducers/rootReducer'; import { RecordCacheServiceFactory } from 'utils/record-cache/context'; +import RecordCache from '../cache/RecordCache'; export interface IUpdateFilter { setID: string | number; @@ -56,6 +59,21 @@ class RecordSet { } ); + static readonly requestRemoval = createAsyncThunk( + `${this.PREFIX}/requestRemoval`, + async (spec: { setId: string }, thunkAPI) => { + const state = thunkAPI.getState() as RootState; + const { recordSets } = state.UserSettings; + if (MOBILE && recordSets[spec.setId].cacheMetadata.status == UserRecordCacheStatus.CACHED) { + const deletionResult = await thunkAPI.dispatch(RecordCache.deleteCache(spec)); + if (RecordCache.deleteCache.rejected.match(deletionResult)) { + throw Error('Cache failed to delete'); + } + } + return spec.setId; + } + ); + static readonly updateFilter = createAction(`${this.PREFIX}/updateFilter`); static readonly removeFilter = createAction(`${this.PREFIX}/removeFilter`); diff --git a/app/src/state/reducers/userSettings.ts b/app/src/state/reducers/userSettings.ts index 3da1423e7..faed2891e 100644 --- a/app/src/state/reducers/userSettings.ts +++ b/app/src/state/reducers/userSettings.ts @@ -104,7 +104,7 @@ function createUserSettingsReducer(configuration: AppConfig): (UserSettingsState draftState.mapCenter = action.payload as [number, number]; } else if (UserSettings.RecordSet.add.match(action)) { draftState.recordSets[Date.now().toString()] ??= action.payload; - } else if (UserSettings.RecordSet.remove.match(action)) { + } else if (UserSettings.RecordSet.requestRemoval.fulfilled.match(action)) { delete draftState.recordSets[action.payload]; } else if (UserSettings.RecordSet.set.match(action)) { Object.keys(action.payload.updatedSet).forEach((key) => { @@ -218,19 +218,6 @@ function createUserSettingsReducer(configuration: AppConfig): (UserSettingsState draftState.recordSets[action.meta.arg.setId].cacheMetadata = { status: UserRecordCacheStatus.NOT_CACHED }; - } else if (UserSettings.RecordSet.syncCacheStatusWithCacheService.fulfilled.match(action)) { - const cacheStatus = action.payload; - for (const cachedSet of cacheStatus) { - if (draftState.recordSets[cachedSet.setId]) { - const { idList, cachedGeoJson, cachedCentroid } = draftState.recordSets[cachedSet.setId].cacheMetadata; - draftState.recordSets[cachedSet.setId].cacheMetadata = { - status: UserRecordCacheStatus.CACHED, - idList, - cachedGeoJson, - cachedCentroid - }; - } - } } else if (Activity.deleteSuccess.match(action)) { draftState.activeActivity = null; draftState.activeActivityDescription = null; diff --git a/app/src/utils/record-cache/sqlite-cache.ts b/app/src/utils/record-cache/sqlite-cache.ts index 49a334406..81693f739 100644 --- a/app/src/utils/record-cache/sqlite-cache.ts +++ b/app/src/utils/record-cache/sqlite-cache.ts @@ -287,17 +287,30 @@ class SQLiteRecordCacheService extends RecordCacheService { if (this.cacheDB == null) { throw new Error(CACHE_UNAVAILABLE); } + const RecordsToTable = { [RecordSetType.Activity]: 'CACHED_RECORDS', [RecordSetType.IAPP]: 'CACHED_IAPP_RECORDS' }; const RECORD_TABLE = RecordsToTable[recordSetType]; - await this.cacheDB.query( - // language=SQLite - `DELETE FROM ${RECORD_TABLE} - WHERE ID IN (${idsToDelete.map(() => '?').join(', ')})`, - [...idsToDelete] - ); + const BATCH_AMOUNT = 100; + + this.cacheDB.beginTransaction(); + try { + for (let i = 0; i < idsToDelete.length; i += BATCH_AMOUNT) { + const sliced = idsToDelete.slice(i, Math.min(i + BATCH_AMOUNT, idsToDelete.length)); + await this.cacheDB.query( + // language=SQLite + `DELETE FROM ${RECORD_TABLE} + WHERE ID IN (${sliced.map(() => '?').join(', ')})`, + [...sliced] + ); + } + this.cacheDB.commitTransaction(); + } catch (e) { + this.cacheDB.rollbackTransaction(); + throw e; + } } private async initializeRecordCache(sqlite: SQLiteConnection) { // Hold Migrations as named variable so we can use length to update the Db version automagically