refactor: Add delete database method

This adds a delete database
method used for migration to
correctly delete the whole
legacy database instead just
empty it.
This commit is contained in:
Krille 2023-12-12 08:30:00 +01:00
parent b3ec966238
commit eb869462aa
No known key found for this signature in database
10 changed files with 165 additions and 112 deletions

View File

@ -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 ?? <CrossSigningKey>[]) {
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 ?? <CrossSigningKey>[]) {
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,
);
}
}

View File

@ -316,4 +316,8 @@ abstract class DatabaseApi {
Future<void> storePresence(String userId, CachedPresence presence);
Future<CachedPresence?> getPresence(String userId);
/// Deletes the whole database. The database needs to be created again after
/// this. Used for migration only.
Future<void> delete();
}

View File

@ -1594,6 +1594,9 @@ class HiveCollectionsDatabase extends DatabaseApi {
return false;
}
}
@override
Future<void> delete() => _collection.deleteFromDisk();
}
class TupleKey {

View File

@ -1474,6 +1474,9 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
// see no need to implement this in a deprecated part
throw UnimplementedError();
}
@override
Future<void> delete() => Hive.deleteFromDisk();
}
dynamic _castValue(dynamic value) {

View File

@ -7,13 +7,16 @@ import 'dart:indexed_db';
class BoxCollection {
final Database _db;
final Set<String> boxNames;
final String _name;
final IdbFactory _idbFactory;
BoxCollection(this._db, this.boxNames);
BoxCollection(this._db, this.boxNames, this._name, this._idbFactory);
static Future<BoxCollection> open(
String name,
Set<String> 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<V> openBox<V>(String name) {
@ -61,15 +64,19 @@ class BoxCollection {
}
Future<void> 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<void> close() async {
assert(_txnCache == null, 'Database closed while in transaction!');
return _db.close();
}
Future<void> delete() => _idbFactory.deleteDatabase(_name);
}
class Box<V> {

View File

@ -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<String>(
@ -1596,4 +1602,7 @@ class MatrixSdkDatabase extends DatabaseApi {
return CachedPresence.fromJson(copyMap(rawPresence));
}
@override
Future<void> delete() => _collection.delete();
}

View File

@ -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<String> boxNames;
final DatabaseFactory? _factory;
BoxCollection(this._db, this.boxNames);
BoxCollection(this._db, this.boxNames, this._factory);
static Future<BoxCollection> open(
String name,
Set<String> 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<V> openBox<V>(String name) {
@ -110,6 +112,9 @@ class BoxCollection {
);
Future<void> close() => _db.close();
Future<void> delete() =>
(_factory ?? databaseFactory).deleteDatabase(_db.path);
}
class Box<V> {

View File

@ -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();
});
});
}

View File

@ -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);
});
});
}

View File

@ -44,7 +44,11 @@ Future<HiveCollectionsDatabase> getHiveCollectionsDatabase(Client? c) async {
// ignore: deprecated_member_use_from_same_package
Future<MatrixSdkDatabase> 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;
}