diff --git a/lib/src/client.dart b/lib/src/client.dart index f79b56c2..770a4be2 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -3006,123 +3006,123 @@ class Client extends MatrixApi { final migrateClient = await legacyDatabase?.getClient(clientName); final database = this.database; - if (migrateClient != null && legacyDatabase != null && database != null) { - Logs().i('Found data in the legacy database!'); - onMigration?.call(); - _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'], - ); - Logs().d('Migrate SSSSCache...'); - for (final type in cacheTypes) { - final ssssCache = await legacyDatabase.getSSSSCache(type); - if (ssssCache != null) { - Logs().d('Migrate $type...'); - await database.storeSSSSCache( - type, - ssssCache.keyId ?? '', - ssssCache.ciphertext ?? '', - ssssCache.content ?? '', + if (migrateClient == null || legacyDatabase == null || database == null) { + await legacyDatabase?.close(); + _initLock = false; + return; + } + Logs().i('Found data in the legacy database!'); + onMigration?.call(); + _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'], + ); + Logs().d('Migrate SSSSCache...'); + for (final type in cacheTypes) { + final ssssCache = await legacyDatabase.getSSSSCache(type); + if (ssssCache != null) { + Logs().d('Migrate $type...'); + await database.storeSSSSCache( + type, + ssssCache.keyId ?? '', + ssssCache.ciphertext ?? '', + ssssCache.content ?? '', + ); + } + } + Logs().d('Migrate OLM sessions...'); + try { + final olmSessions = await legacyDatabase.getAllOlmSessions(); + for (final identityKey in olmSessions.keys) { + final sessions = olmSessions[identityKey]!; + for (final sessionId in sessions.keys) { + final session = sessions[sessionId]!; + await database.storeOlmSession( + identityKey, + session['session_id'] as String, + session['pickle'] as String, + session['last_received'] as int, ); } } - Logs().d('Migrate OLM sessions...'); - try { - final olmSessions = await legacyDatabase.getAllOlmSessions(); - for (final identityKey in olmSessions.keys) { - final sessions = olmSessions[identityKey]!; - for (final sessionId in sessions.keys) { - final session = sessions[sessionId]!; - await database.storeOlmSession( - identityKey, - session['session_id'] as String, - session['pickle'] as String, - session['last_received'] as int, - ); - } + } catch (e, s) { + Logs().e('Unable to migrate OLM sessions!', e, s); + } + Logs().d('Migrate Device Keys...'); + final userDeviceKeys = await legacyDatabase.getUserDeviceKeys(this); + for (final userId in userDeviceKeys.keys) { + Logs().d('Migrate Device Keys of user $userId...'); + final deviceKeysList = userDeviceKeys[userId]; + for (final crossSigningKey + in deviceKeysList?.crossSigningKeys.values ?? []) { + final pubKey = crossSigningKey.publicKey; + if (pubKey != null) { + Logs().d( + 'Migrate cross signing key with usage ${crossSigningKey.usage} and verified ${crossSigningKey.directVerified}...'); + await database.storeUserCrossSigningKey( + userId, + pubKey, + jsonEncode(crossSigningKey.toJson()), + crossSigningKey.directVerified, + crossSigningKey.blocked, + ); } - } catch (e, s) { - Logs().e('Unable to migrate OLM sessions!', e, s); } - Logs().d('Migrate Device Keys...'); - final userDeviceKeys = await legacyDatabase.getUserDeviceKeys(this); - for (final userId in userDeviceKeys.keys) { - Logs().d('Migrate Device Keys of user $userId...'); - final deviceKeysList = userDeviceKeys[userId]; - for (final crossSigningKey - in deviceKeysList?.crossSigningKeys.values ?? []) { - final pubKey = crossSigningKey.publicKey; - if (pubKey != null) { - Logs().d( - 'Migrate cross signing key with usage ${crossSigningKey.usage} and verified ${crossSigningKey.directVerified}...'); - await database.storeUserCrossSigningKey( + + if (deviceKeysList != null) { + for (final deviceKeys in deviceKeysList.deviceKeys.values) { + final deviceId = deviceKeys.deviceId; + if (deviceId != null) { + Logs().d('Migrate device keys for ${deviceKeys.deviceId}...'); + await database.storeUserDeviceKey( userId, - pubKey, - jsonEncode(crossSigningKey.toJson()), - crossSigningKey.directVerified, - crossSigningKey.blocked, + deviceId, + jsonEncode(deviceKeys.toJson()), + deviceKeys.directVerified, + deviceKeys.blocked, + deviceKeys.lastActive.millisecondsSinceEpoch, ); } } - - if (deviceKeysList != null) { - for (final deviceKeys in deviceKeysList.deviceKeys.values) { - final deviceId = deviceKeys.deviceId; - if (deviceId != null) { - Logs().d('Migrate device keys for ${deviceKeys.deviceId}...'); - await database.storeUserDeviceKey( - userId, - deviceId, - jsonEncode(deviceKeys.toJson()), - deviceKeys.directVerified, - deviceKeys.blocked, - deviceKeys.lastActive.millisecondsSinceEpoch, - ); - } - } - Logs().d('Migrate user device keys info...'); - await database.storeUserDeviceKeysInfo( - userId, deviceKeysList.outdated); - } + Logs().d('Migrate user device keys info...'); + await database.storeUserDeviceKeysInfo(userId, deviceKeysList.outdated); } - Logs().d('Migrate inbound group sessions...'); - try { - final sessions = await legacyDatabase.getAllInboundGroupSessions(); - for (var i = 0; i < sessions.length; i++) { - Logs().d('$i / ${sessions.length}'); - final session = sessions[i]; - await database.storeInboundGroupSession( - session.roomId, - session.sessionId, - session.pickle, - session.content, - session.indexes, - session.allowedAtIndex, - session.senderKey, - session.senderClaimedKeys, - ); - } - } catch (e, s) { - Logs().e('Unable to migrate inbound group sessions!', e, s); - } - - await legacyDatabase.clear(); } - await legacyDatabase?.close(); + Logs().d('Migrate inbound group sessions...'); + try { + final sessions = await legacyDatabase.getAllInboundGroupSessions(); + for (var i = 0; i < sessions.length; i++) { + Logs().d('$i / ${sessions.length}'); + final session = sessions[i]; + await database.storeInboundGroupSession( + session.roomId, + session.sessionId, + session.pickle, + session.content, + session.indexes, + session.allowedAtIndex, + session.senderKey, + session.senderClaimedKeys, + ); + } + } catch (e, s) { + Logs().e('Unable to migrate inbound group sessions!', e, s); + } + + await legacyDatabase.delete(); + _initLock = false; - if (migrateClient != null) { - return init( - waitForFirstSync: false, - waitUntilLoadCompletedLoaded: false, - ); - } + return init( + waitForFirstSync: false, + waitUntilLoadCompletedLoaded: false, + ); } } diff --git a/lib/src/database/database_api.dart b/lib/src/database/database_api.dart index b33f3072..2c41c7d4 100644 --- a/lib/src/database/database_api.dart +++ b/lib/src/database/database_api.dart @@ -316,4 +316,8 @@ abstract class DatabaseApi { Future storePresence(String userId, CachedPresence presence); Future getPresence(String userId); + + /// Deletes the whole database. The database needs to be created again after + /// this. Used for migration only. + Future delete(); } diff --git a/lib/src/database/hive_collections_database.dart b/lib/src/database/hive_collections_database.dart index d447b3ab..e5b724eb 100644 --- a/lib/src/database/hive_collections_database.dart +++ b/lib/src/database/hive_collections_database.dart @@ -1594,6 +1594,9 @@ class HiveCollectionsDatabase extends DatabaseApi { return false; } } + + @override + Future delete() => _collection.deleteFromDisk(); } class TupleKey { diff --git a/lib/src/database/hive_database.dart b/lib/src/database/hive_database.dart index e015dbcb..316449a7 100644 --- a/lib/src/database/hive_database.dart +++ b/lib/src/database/hive_database.dart @@ -1474,6 +1474,9 @@ class FamedlySdkHiveDatabase extends DatabaseApi { // see no need to implement this in a deprecated part throw UnimplementedError(); } + + @override + Future delete() => Hive.deleteFromDisk(); } dynamic _castValue(dynamic value) { diff --git a/lib/src/database/indexeddb_box.dart b/lib/src/database/indexeddb_box.dart index b67c1106..a804e1a0 100644 --- a/lib/src/database/indexeddb_box.dart +++ b/lib/src/database/indexeddb_box.dart @@ -7,13 +7,16 @@ import 'dart:indexed_db'; class BoxCollection { final Database _db; final Set boxNames; + final String _name; + final IdbFactory _idbFactory; - BoxCollection(this._db, this.boxNames); + BoxCollection(this._db, this.boxNames, this._name, this._idbFactory); static Future open( String name, Set boxNames, { Object? sqfliteDatabase, + Object? sqfliteFactory, IdbFactory? idbFactory, }) async { idbFactory ??= window.indexedDB!; @@ -24,7 +27,7 @@ class BoxCollection { db.createObjectStore(name, autoIncrement: true); } }); - return BoxCollection(db, boxNames); + return BoxCollection(db, boxNames, name, idbFactory); } Box openBox(String name) { @@ -61,15 +64,19 @@ class BoxCollection { } Future clear() async { + final txn = _db.transaction(boxNames, 'readwrite'); for (final name in boxNames) { - _db.deleteObjectStore(name); + unawaited(txn.objectStore(name).clear()); } + await txn.completed; } Future close() async { assert(_txnCache == null, 'Database closed while in transaction!'); return _db.close(); } + + Future delete() => _idbFactory.deleteDatabase(_name); } class Box { diff --git a/lib/src/database/matrix_sdk_database.dart b/lib/src/database/matrix_sdk_database.dart index 78d34d67..6ab9e7d2 100644 --- a/lib/src/database/matrix_sdk_database.dart +++ b/lib/src/database/matrix_sdk_database.dart @@ -144,10 +144,15 @@ class MatrixSdkDatabase extends DatabaseApi { /// typed. final dynamic idbFactory; + /// Custom SQFlite Database Factory used for high level operations on IO + /// like delete. Set it if you want to use sqlite FFI. + final DatabaseFactory? sqfliteFactory; + MatrixSdkDatabase( this.name, { this.database, this.idbFactory, + this.sqfliteFactory, this.maxFileSize = 0, this.fileStoragePath, this.deleteFilesAfterDuration, @@ -179,6 +184,7 @@ class MatrixSdkDatabase extends DatabaseApi { _seenDeviceKeysBoxName, }, sqfliteDatabase: database, + sqfliteFactory: sqfliteFactory, idbFactory: idbFactory, ); _clientBox = _collection.openBox( @@ -1596,4 +1602,7 @@ class MatrixSdkDatabase extends DatabaseApi { return CachedPresence.fromJson(copyMap(rawPresence)); } + + @override + Future delete() => _collection.delete(); } diff --git a/lib/src/database/sqflite_box.dart b/lib/src/database/sqflite_box.dart index 07f1d45c..16e1a9ca 100644 --- a/lib/src/database/sqflite_box.dart +++ b/lib/src/database/sqflite_box.dart @@ -1,20 +1,22 @@ import 'dart:async'; import 'dart:convert'; -import 'package:sqflite_common/sqlite_api.dart'; +import 'package:sqflite_common/sqflite.dart'; /// Key-Value store abstraction over Sqflite so that the sdk database can use /// a single interface for all platforms. API is inspired by Hive. class BoxCollection { final Database _db; final Set boxNames; + final DatabaseFactory? _factory; - BoxCollection(this._db, this.boxNames); + BoxCollection(this._db, this.boxNames, this._factory); static Future open( String name, Set boxNames, { Object? sqfliteDatabase, + DatabaseFactory? sqfliteFactory, dynamic idbFactory, }) async { if (sqfliteDatabase is! Database) { @@ -28,7 +30,7 @@ class BoxCollection { batch.execute('CREATE INDEX IF NOT EXISTS k_index ON $name (k)'); } await batch.commit(noResult: true); - return BoxCollection(sqfliteDatabase, boxNames); + return BoxCollection(sqfliteDatabase, boxNames, sqfliteFactory); } Box openBox(String name) { @@ -110,6 +112,9 @@ class BoxCollection { ); Future close() => _db.close(); + + Future delete() => + (_factory ?? databaseFactory).deleteDatabase(_db.path); } class Box { diff --git a/test/box_test.dart b/test/box_test.dart index d58cf616..2ab4ec3b 100644 --- a/test/box_test.dart +++ b/test/box_test.dart @@ -15,6 +15,7 @@ void main() { 'testbox', boxNames, sqfliteDatabase: db, + sqfliteFactory: databaseFactoryFfi, ); }); @@ -76,5 +77,9 @@ void main() { expect(await box.get('fluffy'), null); expect(await box.get('loki'), null); }); + + test('Box.delete', () async { + await collection.delete(); + }); }); } diff --git a/test/database_api_test.dart b/test/database_api_test.dart index 6e3c9999..00db1e3f 100644 --- a/test/database_api_test.dart +++ b/test/database_api_test.dart @@ -472,6 +472,19 @@ void main() { test('Close', () async { await database.close(); }); + test('Delete', () async { + final database = await getMatrixSdkDatabase(null); + await database.storeAccountData( + 'm.test.data', + jsonEncode({'foo': 'bar'}), + ); + await database.delete(); + + // Check if previously stored data is gone: + final reopenedDatabase = await getMatrixSdkDatabase(null); + final dump = await reopenedDatabase.getAccountData(); + expect(dump.isEmpty, true); + }); }); } diff --git a/test/fake_database.dart b/test/fake_database.dart index af428557..2d041e62 100644 --- a/test/fake_database.dart +++ b/test/fake_database.dart @@ -44,7 +44,11 @@ Future getHiveCollectionsDatabase(Client? c) async { // ignore: deprecated_member_use_from_same_package Future getMatrixSdkDatabase(Client? c) async { final database = await databaseFactoryFfi.openDatabase(':memory:'); - final db = MatrixSdkDatabase('unit_test.${c?.hashCode}', database: database); + final db = MatrixSdkDatabase( + 'unit_test.${c?.hashCode}', + database: database, + sqfliteFactory: databaseFactoryFfi, + ); await db.open(); return db; }