diff --git a/src/script/backup/BackupRepository.test.ts b/src/script/backup/BackupRepository.test.ts index 2e035a6bad8..c682d9aa80b 100644 --- a/src/script/backup/BackupRepository.test.ts +++ b/src/script/backup/BackupRepository.test.ts @@ -28,7 +28,7 @@ import {WebWorker} from 'Util/worker'; import {BackUpHeader, DecodedHeader, ENCRYPTED_BACKUP_FORMAT, ENCRYPTED_BACKUP_VERSION} from './BackUpHeader'; import {BackupRepository, Filename} from './BackupRepository'; import {BackupService} from './BackupService'; -import {CancelError, DifferentAccountError, IncompatibleBackupError, IncompatiblePlatformError} from './Error'; +import {CancelError, DifferentAccountError, IncompatiblePlatformError} from './Error'; import {handleZipEvent} from './zipWorker'; import {ConversationRepository} from '../conversation/ConversationRepository'; @@ -172,12 +172,6 @@ describe('BackupRepository', () => { metaChanges: {user_id: 'fail'}, }, ], - [ - { - expectedError: IncompatibleBackupError, - metaChanges: {version: 13}, // version 14 contains a migration script, thus will generate an error - }, - ], [ { expectedError: IncompatiblePlatformError, diff --git a/src/script/backup/BackupRepository.ts b/src/script/backup/BackupRepository.ts index 893665f530e..8c5fb03f5d2 100644 --- a/src/script/backup/BackupRepository.ts +++ b/src/script/backup/BackupRepository.ts @@ -311,7 +311,7 @@ export class BackupRepository { throw new InvalidMetaDataError(); } - await this.verifyMetadata(user, files); + const archiveVersion = await this.verifyMetadata(user, files); const fileDescriptors = Object.entries(files) .filter(([filename]) => filename !== Filename.METADATA) .map(([filename, content]) => { @@ -326,7 +326,7 @@ export class BackupRepository { const nbEntities = fileDescriptors.reduce((acc, {entities}) => acc + entities.length, 0); initCallback(nbEntities); - await this.importHistoryData(fileDescriptors, progressCallback); + await this.importHistoryData(archiveVersion, fileDescriptors, progressCallback); } private async createDecryptedBackup( @@ -357,6 +357,7 @@ export class BackupRepository { } private async importHistoryData( + archiveVersion: number, fileDescriptors: FileDescriptor[], progressCallback: ProgressCallback, ): Promise { @@ -377,6 +378,9 @@ export class BackupRepository { } } + // Run all the database migrations on the imported data + await this.backupService.runDbSchemaUpdates(archiveVersion); + await this.conversationRepository.updateConversations(importedConversations); await this.conversationRepository.initAllLocal1To1Conversations(); // doesn't need to be awaited @@ -474,15 +478,16 @@ export class BackupRepository { return omit(entity, 'primary_key'); } - private async verifyMetadata(user: User, files: Record): Promise { + private async verifyMetadata(user: User, files: Record): Promise { const rawData = files[Filename.METADATA]; const metaData = new TextDecoder().decode(rawData); const parsedMetaData = JSON.parse(metaData); - this._verifyMetadata(user, parsedMetaData); + const archiveVersion = this._verifyMetadata(user, parsedMetaData); this.logger.log('Validated metadata during history import', files); + return archiveVersion; } - private _verifyMetadata(user: User, archiveMetadata: Metadata): void { + private _verifyMetadata(user: User, archiveMetadata: Metadata): number { const localMetadata = this.createMetaData(user, ''); const isExpectedUserId = archiveMetadata.user_id === localMetadata.user_id; if (!isExpectedUserId) { @@ -498,18 +503,7 @@ export class BackupRepository { throw new IncompatiblePlatformError(message); } - const lowestDbVersion = Math.min(archiveMetadata.version, localMetadata.version); - const involvesDatabaseMigration = StorageSchemata.SCHEMATA.reduce((involvesMigration, schemaData) => { - if (schemaData.version > lowestDbVersion) { - return involvesMigration || !!schemaData.upgrade; - } - return involvesMigration; - }, false); - - if (involvesDatabaseMigration) { - const message = 'History cannot be restored: Database version mismatch'; - throw new IncompatibleBackupError(message); - } + return archiveMetadata.version; } private mapDecodingError(decodingError: string) { diff --git a/src/script/backup/BackupService.ts b/src/script/backup/BackupService.ts index fc180deb4b7..1c8e31f8603 100644 --- a/src/script/backup/BackupService.ts +++ b/src/script/backup/BackupService.ts @@ -67,6 +67,15 @@ export class BackupService { ] as const; } + async runDbSchemaUpdates(archiveVersion: number): Promise { + const {db} = this.storageService; + if (!db) { + this.logger.warn('Database schema will not run because the database is not initialized'); + return; + } + return db.runDbSchemaUpdates(archiveVersion); + } + /** * Will import all entities in the Database. * If a primaryKey generator is given, it will only import the entities that are not already in the DB diff --git a/src/script/storage/DexieDatabase.ts b/src/script/storage/DexieDatabase.ts index 213f603bfc6..d04e1425995 100644 --- a/src/script/storage/DexieDatabase.ts +++ b/src/script/storage/DexieDatabase.ts @@ -49,15 +49,7 @@ export class DexieDatabase extends Dexie { super(dbName); this.logger = getLogger(`Dexie (${dbName})`); - StorageSchemata.SCHEMATA.forEach(({schema, upgrade, version}) => { - const versionInstance = this.version(version).stores(schema); - if (upgrade) { - versionInstance.upgrade((transaction: Transaction) => { - this.logger.warn(`Database upgrade to version '${version}'`); - upgrade(transaction, this); - }); - } - }); + void this.initDbSchema(); this.amplify = this.table(StorageSchemata.OBJECT_STORE.AMPLIFY); this.clients = this.table(StorageSchemata.OBJECT_STORE.CLIENTS); @@ -70,4 +62,28 @@ export class DexieDatabase extends Dexie { this.users = this.table(StorageSchemata.OBJECT_STORE.USERS); this.groupIds = this.table(StorageSchemata.OBJECT_STORE.GROUP_IDS); } + + private readonly initDbSchema = async (): Promise => { + StorageSchemata.SCHEMATA.forEach(({schema, upgrade, version}) => { + const versionInstance = this.version(version).stores(schema); + if (upgrade) { + versionInstance.upgrade((transaction: Transaction) => { + this.logger.warn(`Database upgrade to version '${version}'`); + upgrade(transaction, this); + }); + } + }); + }; + + public readonly runDbSchemaUpdates = async (archiveVersion: number): Promise => { + for (const {upgrade, version} of StorageSchemata.SCHEMATA) { + // If the archive version is greater than the current version, run the upgrade + if (upgrade && version > archiveVersion) { + await this.transaction('rw', this.tables, transaction => { + this.logger.info(`Running DB schema update for version '${version}'`); + upgrade(transaction, this); + }); + } + } + }; }