From 967712adfee02f321cc3b10a6d9051ba420418f9 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 10 Jun 2021 08:40:24 +0200 Subject: [PATCH] feat: Implement database migration This allows the user to give a legacyDatabaseBuilder to the client object and in the init proccess the client checks by itself if there is old data in the legacy database. If yes then it migrates them and then deletes the old database. This uses the database_api and is agnostic to the database implementation. --- lib/src/client.dart | 51 +++++++++++++++++++++++++++++++++++++++++++ test/client_test.dart | 32 +++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/lib/src/client.dart b/lib/src/client.dart index 106a7a59..034bff0a 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -57,7 +57,9 @@ class Client extends MatrixApi { int get id => _id; final FutureOr Function(Client) databaseBuilder; + final FutureOr Function(Client) legacyDatabaseBuilder; final FutureOr Function(Client) databaseDestroyer; + final FutureOr Function(Client) legacyDatabaseDestroyer; DatabaseApi _database; DatabaseApi get database => _database; @@ -94,6 +96,7 @@ class Client extends MatrixApi { /// Create a client /// [clientName] = unique identifier of this client /// [databaseBuilder]: A function that creates the database instance, that will be used. + /// [legacyDatabaseBuilder]: Use this for your old database implementation to perform an automatic migration /// [databaseDestroyer]: A function that can be used to destroy a database instance, for example by deleting files from disk. /// [enableE2eeRecovery]: Enable additional logic to try to recover from bad e2ee sessions /// [verificationMethods]: A set of all the verification methods this client can handle. Includes: @@ -128,6 +131,8 @@ class Client extends MatrixApi { this.clientName, { this.databaseBuilder, this.databaseDestroyer, + this.legacyDatabaseBuilder, + this.legacyDatabaseDestroyer, this.enableE2eeRecovery = false, this.verificationMethods, http.Client httpClient, @@ -877,6 +882,52 @@ class Client extends MatrixApi { onLoginStateChanged.add(LoginState.loggedOut); Logs().i('User is not logged in.'); _initLock = false; + if (legacyDatabaseBuilder != null) { + Logs().i('Check legacy database for migration data'); + final legacyDatabase = await legacyDatabaseBuilder(this); + final migrateClient = await legacyDatabase.getClient(clientName); + if (migrateClient != null) { + Logs().i('Found data in the legacy database'); + _id = migrateClient['client_id']; + await database.insertClient( + clientName, + migrateClient['homeserver_url'], + migrateClient['token'], + migrateClient['user_id'], + migrateClient['device_id'], + migrateClient['device_name'], + null, + migrateClient['olm_account'], + ); + for (final type in cacheTypes) { + Logs().i('Migrate $type'); + final ssssCache = await legacyDatabase.getSSSSCache(_id, type); + if (ssssCache != null) { + await database.storeSSSSCache( + _id, + type, + ssssCache.keyId, + ssssCache.ciphertext, + ssssCache.content, + ); + } + } + + await legacyDatabase.clear(_id); + await legacyDatabaseDestroyer?.call(this); + } + await legacyDatabase.close(); + if (migrateClient != null) { + return init( + newToken: migrateClient['token'], + newHomeserver: Uri.parse(migrateClient['homeserver_url']), + newUserID: migrateClient['user_id'], + newDeviceID: migrateClient['device_id'], + newDeviceName: migrateClient['device_name'], + newOlmAccount: migrateClient['olm_account'], + ); + } + } return; } _initLock = false; diff --git a/test/client_test.dart b/test/client_test.dart index 87468e9f..38d06cee 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -634,5 +634,37 @@ void main() { test('dispose', () async { await matrix.dispose(closeDatabase: true); }); + + test('Database Migration', () async { + final database = await getDatabase(null); + final moorClient = Client( + 'testclient', + httpClient: FakeMatrixApi(), + databaseBuilder: (_) => database, + ); + FakeMatrixApi.client = moorClient; + await moorClient.checkHomeserver('https://fakeServer.notExisting', + checkWellKnown: false); + await moorClient.init( + newToken: 'abcd', + newUserID: '@test:fakeServer.notExisting', + newHomeserver: moorClient.homeserver, + newDeviceName: 'Text Matrix Client', + newDeviceID: 'GHTYAJCE', + newOlmAccount: pickledOlmAccount, + ); + await Future.delayed(Duration(milliseconds: 200)); + await moorClient.dispose(closeDatabase: false); + + final hiveClient = Client( + 'testclient', + httpClient: FakeMatrixApi(), + databaseBuilder: getDatabase, + legacyDatabaseBuilder: (_) => database, + ); + await hiveClient.init(); + await Future.delayed(Duration(milliseconds: 200)); + expect(hiveClient.isLogged(), true); + }); }); }