diff --git a/lib/src/client.dart b/lib/src/client.dart index 1a40a2df..e2eb3dad 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -816,6 +816,55 @@ class Client extends MatrixApi { } } + /// dumps the local database and exports it into a String. + /// + /// WARNING: never re-import the dump twice + /// + /// This can be useful to migrate a session from one device to a future one. + Future exportDump() async { + if (database != null) { + await abortSync(); + await dispose(closeDatabase: false); + + final export = await database!.exportDump(); + + await clear(); + return export; + } + return null; + } + + /// imports a dumped session + /// + /// WARNING: never re-import the dump twice + Future importDump(String export) async { + try { + // stopping sync loop and subscriptions while keeping DB open + await dispose(closeDatabase: false); + } finally { + _database ??= await databaseBuilder!.call(this); + + final success = await database!.importDump(export); + + if (success) { + // closing including DB + await dispose(); + + try { + bearerToken = null; + + await init( + waitForFirstSync: false, + waitUntilLoadCompletedLoaded: false, + ); + } catch (e) { + return false; + } + } + return success; + } + } + /// Uploads a new user avatar for this user. Leave file null to remove the /// current avatar. Future setAvatar(MatrixFile? file) async { diff --git a/lib/src/database/database_api.dart b/lib/src/database/database_api.dart index eff50d85..c8a65477 100644 --- a/lib/src/database/database_api.dart +++ b/lib/src/database/database_api.dart @@ -310,4 +310,8 @@ abstract class DatabaseApi { Future close(); Future transaction(Future Function() action); + + Future exportDump(); + + Future importDump(String export); } diff --git a/lib/src/database/fluffybox_database.dart b/lib/src/database/fluffybox_database.dart index 9be9190d..804dcdda 100644 --- a/lib/src/database/fluffybox_database.dart +++ b/lib/src/database/fluffybox_database.dart @@ -39,7 +39,7 @@ class FluffyBoxDatabase extends DatabaseApi { final String name; final String path; final HiveCipher? key; - late final BoxCollection _collection; + late BoxCollection _collection; late Box _clientBox; late Box _accountDataBox; late Box _roomsBox; @@ -1458,4 +1458,108 @@ class FluffyBoxDatabase extends DatabaseApi { if (raw == null) return null; return raw; } + + @override + Future exportDump() async { + final dataMap = { + _clientBoxName: await _clientBox.getAllValues(), + _accountDataBoxName: await _accountDataBox.getAllValues(), + _roomsBoxName: await _roomsBox.getAllValues(), + _roomStateBoxName: await _roomStateBox.getAllValues(), + _roomMembersBoxName: await _roomMembersBox.getAllValues(), + _toDeviceQueueBoxName: await _toDeviceQueueBox.getAllValues(), + _roomAccountDataBoxName: await _roomAccountDataBox.getAllValues(), + _inboundGroupSessionsBoxName: + await _inboundGroupSessionsBox.getAllValues(), + _outboundGroupSessionsBoxName: + await _outboundGroupSessionsBox.getAllValues(), + _olmSessionsBoxName: await _olmSessionsBox.getAllValues(), + _userDeviceKeysBoxName: await _userDeviceKeysBox.getAllValues(), + _userDeviceKeysOutdatedBoxName: + await _userDeviceKeysOutdatedBox.getAllValues(), + _userCrossSigningKeysBoxName: + await _userCrossSigningKeysBox.getAllValues(), + _ssssCacheBoxName: await _ssssCacheBox.getAllValues(), + _presencesBoxName: await _presencesBox.getAllValues(), + _timelineFragmentsBoxName: await _timelineFragmentsBox.getAllValues(), + _eventsBoxName: await _eventsBox.getAllValues(), + _seenDeviceIdsBoxName: await _seenDeviceIdsBox.getAllValues(), + _seenDeviceKeysBoxName: await _seenDeviceKeysBox.getAllValues(), + }; + final json = jsonEncode(dataMap); + await clear(); + return json; + } + + @override + Future importDump(String export) async { + try { + await clear(); + await open(); + final json = Map.from(jsonDecode(export)).cast(); + for (final key in json[_clientBoxName]!.keys) { + await _clientBox.put(key, json[_clientBoxName]![key]); + } + for (final key in json[_accountDataBoxName]!.keys) { + await _accountDataBox.put(key, json[_accountDataBoxName]![key]); + } + for (final key in json[_roomsBoxName]!.keys) { + await _roomsBox.put(key, json[_roomsBoxName]![key]); + } + for (final key in json[_roomStateBoxName]!.keys) { + await _roomStateBox.put(key, json[_roomStateBoxName]![key]); + } + for (final key in json[_roomMembersBoxName]!.keys) { + await _roomMembersBox.put(key, json[_roomMembersBoxName]![key]); + } + for (final key in json[_toDeviceQueueBoxName]!.keys) { + await _toDeviceQueueBox.put(key, json[_toDeviceQueueBoxName]![key]); + } + for (final key in json[_roomAccountDataBoxName]!.keys) { + await _roomAccountDataBox.put(key, json[_roomAccountDataBoxName]![key]); + } + for (final key in json[_inboundGroupSessionsBoxName]!.keys) { + await _inboundGroupSessionsBox.put( + key, json[_inboundGroupSessionsBoxName]![key]); + } + for (final key in json[_outboundGroupSessionsBoxName]!.keys) { + await _outboundGroupSessionsBox.put( + key, json[_outboundGroupSessionsBoxName]![key]); + } + for (final key in json[_olmSessionsBoxName]!.keys) { + await _olmSessionsBox.put(key, json[_olmSessionsBoxName]![key]); + } + for (final key in json[_userDeviceKeysBoxName]!.keys) { + await _userDeviceKeysBox.put(key, json[_userDeviceKeysBoxName]![key]); + } + for (final key in json[_userDeviceKeysOutdatedBoxName]!.keys) { + await _userDeviceKeysOutdatedBox.put( + key, json[_userDeviceKeysOutdatedBoxName]![key]); + } + for (final key in json[_userCrossSigningKeysBoxName]!.keys) { + await _userCrossSigningKeysBox.put( + key, json[_userCrossSigningKeysBoxName]![key]); + } + for (final key in json[_ssssCacheBoxName]!.keys) { + await _ssssCacheBox.put(key, json[_ssssCacheBoxName]![key]); + } + for (final key in json[_presencesBoxName]!.keys) { + await _presencesBox.put(key, json[_presencesBoxName]![key]); + } + for (final key in json[_timelineFragmentsBoxName]!.keys) { + await _timelineFragmentsBox.put( + key, json[_timelineFragmentsBoxName]![key]); + } + for (final key in json[_seenDeviceIdsBoxName]!.keys) { + await _seenDeviceIdsBox.put(key, json[_seenDeviceIdsBoxName]![key]); + } + for (final key in json[_seenDeviceKeysBoxName]!.keys) { + await _seenDeviceKeysBox.put(key, json[_seenDeviceKeysBoxName]![key]); + } + return true; + } catch (e, s) { + Logs().e('Database import error: ', e, s); + return false; + } + } } diff --git a/lib/src/database/hive_collections_database.dart b/lib/src/database/hive_collections_database.dart index b9af4683..8bf7e7ae 100644 --- a/lib/src/database/hive_collections_database.dart +++ b/lib/src/database/hive_collections_database.dart @@ -1460,6 +1460,110 @@ class HiveCollectionsDatabase extends DatabaseApi { if (raw == null) return null; return raw; } + + @override + Future exportDump() async { + final dataMap = { + _clientBoxName: await _clientBox.getAllValues(), + _accountDataBoxName: await _accountDataBox.getAllValues(), + _roomsBoxName: await _roomsBox.getAllValues(), + _roomStateBoxName: await _roomStateBox.getAllValues(), + _roomMembersBoxName: await _roomMembersBox.getAllValues(), + _toDeviceQueueBoxName: await _toDeviceQueueBox.getAllValues(), + _roomAccountDataBoxName: await _roomAccountDataBox.getAllValues(), + _inboundGroupSessionsBoxName: + await _inboundGroupSessionsBox.getAllValues(), + _outboundGroupSessionsBoxName: + await _outboundGroupSessionsBox.getAllValues(), + _olmSessionsBoxName: await _olmSessionsBox.getAllValues(), + _userDeviceKeysBoxName: await _userDeviceKeysBox.getAllValues(), + _userDeviceKeysOutdatedBoxName: + await _userDeviceKeysOutdatedBox.getAllValues(), + _userCrossSigningKeysBoxName: + await _userCrossSigningKeysBox.getAllValues(), + _ssssCacheBoxName: await _ssssCacheBox.getAllValues(), + _presencesBoxName: await _presencesBox.getAllValues(), + _timelineFragmentsBoxName: await _timelineFragmentsBox.getAllValues(), + _eventsBoxName: await _eventsBox.getAllValues(), + _seenDeviceIdsBoxName: await _seenDeviceIdsBox.getAllValues(), + _seenDeviceKeysBoxName: await _seenDeviceKeysBox.getAllValues(), + }; + final json = jsonEncode(dataMap); + await clear(); + return json; + } + + @override + Future importDump(String export) async { + try { + await clear(); + await open(); + final json = Map.from(jsonDecode(export)).cast(); + for (final key in json[_clientBoxName]!.keys) { + await _clientBox.put(key, json[_clientBoxName]![key]); + } + for (final key in json[_accountDataBoxName]!.keys) { + await _accountDataBox.put(key, json[_accountDataBoxName]![key]); + } + for (final key in json[_roomsBoxName]!.keys) { + await _roomsBox.put(key, json[_roomsBoxName]![key]); + } + for (final key in json[_roomStateBoxName]!.keys) { + await _roomStateBox.put(key, json[_roomStateBoxName]![key]); + } + for (final key in json[_roomMembersBoxName]!.keys) { + await _roomMembersBox.put(key, json[_roomMembersBoxName]![key]); + } + for (final key in json[_toDeviceQueueBoxName]!.keys) { + await _toDeviceQueueBox.put(key, json[_toDeviceQueueBoxName]![key]); + } + for (final key in json[_roomAccountDataBoxName]!.keys) { + await _roomAccountDataBox.put(key, json[_roomAccountDataBoxName]![key]); + } + for (final key in json[_inboundGroupSessionsBoxName]!.keys) { + await _inboundGroupSessionsBox.put( + key, json[_inboundGroupSessionsBoxName]![key]); + } + for (final key in json[_outboundGroupSessionsBoxName]!.keys) { + await _outboundGroupSessionsBox.put( + key, json[_outboundGroupSessionsBoxName]![key]); + } + for (final key in json[_olmSessionsBoxName]!.keys) { + await _olmSessionsBox.put(key, json[_olmSessionsBoxName]![key]); + } + for (final key in json[_userDeviceKeysBoxName]!.keys) { + await _userDeviceKeysBox.put(key, json[_userDeviceKeysBoxName]![key]); + } + for (final key in json[_userDeviceKeysOutdatedBoxName]!.keys) { + await _userDeviceKeysOutdatedBox.put( + key, json[_userDeviceKeysOutdatedBoxName]![key]); + } + for (final key in json[_userCrossSigningKeysBoxName]!.keys) { + await _userCrossSigningKeysBox.put( + key, json[_userCrossSigningKeysBoxName]![key]); + } + for (final key in json[_ssssCacheBoxName]!.keys) { + await _ssssCacheBox.put(key, json[_ssssCacheBoxName]![key]); + } + for (final key in json[_presencesBoxName]!.keys) { + await _presencesBox.put(key, json[_presencesBoxName]![key]); + } + for (final key in json[_timelineFragmentsBoxName]!.keys) { + await _timelineFragmentsBox.put( + key, json[_timelineFragmentsBoxName]![key]); + } + for (final key in json[_seenDeviceIdsBoxName]!.keys) { + await _seenDeviceIdsBox.put(key, json[_seenDeviceIdsBoxName]![key]); + } + for (final key in json[_seenDeviceKeysBoxName]!.keys) { + await _seenDeviceKeysBox.put(key, json[_seenDeviceKeysBoxName]![key]); + } + return true; + } catch (e, s) { + Logs().e('Database import error: ', e, s); + return false; + } + } } class TupleKey { diff --git a/lib/src/database/hive_database.dart b/lib/src/database/hive_database.dart index 29100931..d8b285c0 100644 --- a/lib/src/database/hive_database.dart +++ b/lib/src/database/hive_database.dart @@ -1405,6 +1405,18 @@ class FamedlySdkHiveDatabase extends DatabaseApi { if (raw == null) return null; return raw as String; } + + @override + Future exportDump() { + // see no need to implement this in a deprecated part + throw UnimplementedError(); + } + + @override + Future importDump(String export) { + // see no need to implement this in a deprecated part + throw UnimplementedError(); + } } dynamic _castValue(dynamic value) {