Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runfix: import backup from migrated db version [WPB-5906] #16400

Merged
merged 3 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions src/script/backup/BackupRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
28 changes: 11 additions & 17 deletions src/script/backup/BackupRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]) => {
Expand All @@ -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(
Expand Down Expand Up @@ -357,6 +357,7 @@ export class BackupRepository {
}

private async importHistoryData(
archiveVersion: number,
fileDescriptors: FileDescriptor[],
progressCallback: ProgressCallback,
): Promise<void> {
Expand All @@ -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
Expand Down Expand Up @@ -474,15 +478,16 @@ export class BackupRepository {
return omit(entity, 'primary_key');
}

private async verifyMetadata(user: User, files: Record<string, Uint8Array>): Promise<void> {
private async verifyMetadata(user: User, files: Record<string, Uint8Array>): Promise<number> {
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) {
Expand All @@ -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) {
Expand Down
9 changes: 9 additions & 0 deletions src/script/backup/BackupService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ export class BackupService {
] as const;
}

async runDbSchemaUpdates(archiveVersion: number): Promise<void> {
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
Expand Down
34 changes: 25 additions & 9 deletions src/script/storage/DexieDatabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<void> => {
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<void> => {
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);
});
}
}
};
}
Loading