diff --git a/lib/backup/persist-shared-migrations.js b/lib/backup/persist-shared-migrations.js new file mode 100644 --- /dev/null +++ b/lib/backup/persist-shared-migrations.js @@ -0,0 +1,15 @@ +// @flow + +import type { DatabaseIdentifier } from '../types/database-identifier-types.js'; +import type { StoreOperations } from '../types/store-ops-types.js'; + +export type SharedMigrationFunction = ( + databaseIdentifier: DatabaseIdentifier, +) => Promise; + +export type SharedMigrationsManifest = { + +[number | string]: SharedMigrationFunction, +}; +const sharedMigrations: SharedMigrationsManifest = {}; + +export { sharedMigrations }; diff --git a/lib/backup/restored-migrations.js b/lib/backup/restored-migrations.js new file mode 100644 --- /dev/null +++ b/lib/backup/restored-migrations.js @@ -0,0 +1,85 @@ +// @flow + +import { sharedMigrations } from './persist-shared-migrations.js'; +import { databaseIdentifier } from '../types/database-identifier-types.js'; +import { syncedMetadataNames } from '../types/synced-metadata-types.js'; +import { getConfig } from '../utils/config.js'; +import { getMessageForException } from '../utils/errors.js'; + +async function getRestoredStoreVersion(): Promise { + const { sqliteAPI } = getConfig(); + const storeVersion = await sqliteAPI.getSyncedMetadata( + syncedMetadataNames.STORE_VERSION, + databaseIdentifier.RESTORED, + ); + if (!storeVersion) { + throw new Error('storeVersion is missing'); + } + return parseInt(storeVersion, 10); +} + +async function runRestoredBackupMigrations() { + const currentStoreVersion = getConfig().platformDetails.stateVersion; + if (!currentStoreVersion) { + throw new Error('currentStoreVersion is missing'); + } + + const restoredStoreVersion = await getRestoredStoreVersion(); + if (restoredStoreVersion === currentStoreVersion) { + console.log('backup-restore: versions match, noop migration'); + return; + } + + if (restoredStoreVersion > currentStoreVersion) { + console.log('backup-restore: current app is older than backup'); + throw new Error('app out-of-date'); + } + + console.log( + `backup-restore: migrating from ${restoredStoreVersion} to ${currentStoreVersion}`, + ); + + const migrationKeys = Object.keys(sharedMigrations) + .map(ver => parseInt(ver)) + .filter(key => currentStoreVersion >= key && key > restoredStoreVersion) + .sort((a, b) => a - b); + + for (const versionKey of migrationKeys) { + console.log('backup-restore: running migration for versionKey', versionKey); + + if (!versionKey) { + continue; + } + try { + const ops = await sharedMigrations[versionKey]( + databaseIdentifier.RESTORED, + ); + const versionUpdateOp = { + type: 'replace_synced_metadata_entry', + payload: { + name: syncedMetadataNames.STORE_VERSION, + data: versionKey.toString(), + }, + }; + const dbOps = { + ...ops, + syncedMetadataStoreOperations: [ + ...(ops.syncedMetadataStoreOperations ?? []), + versionUpdateOp, + ], + }; + await getConfig().sqliteAPI.processDBStoreOperations( + dbOps, + databaseIdentifier.RESTORED, + ); + } catch (exception) { + throw new Error( + `Error while running migration: ${versionKey}: ${ + getMessageForException(exception) ?? 'unknown error' + }`, + ); + } + } +} + +export { runRestoredBackupMigrations }; diff --git a/lib/backup/use-user-data-restore.js b/lib/backup/use-user-data-restore.js --- a/lib/backup/use-user-data-restore.js +++ b/lib/backup/use-user-data-restore.js @@ -2,6 +2,7 @@ import * as React from 'react'; +import { runRestoredBackupMigrations } from './restored-migrations.js'; import { setClientDBStoreActionType } from '../actions/client-db-store-actions.js'; import { useDebugLogs } from '../components/debug-logs-context.js'; import { databaseIdentifier } from '../types/database-identifier-types.js'; @@ -91,7 +92,7 @@ `Main store version (${mainStoreVersion}) is higher than ` + `restored store version (${restoredStoreVersion}), migrating data`, ); - //TODO: call function from D14780 + await runRestoredBackupMigrations(); } else if (mainStoreVersion < restoredStoreVersion) { addLog( 'User Data Restore',