Merge branch 'session-export' into 'main'
feat: implement session export See merge request famedly/company/frontend/famedlysdk!1019
This commit is contained in:
commit
2991fd9ca8
|
|
@ -519,14 +519,13 @@ class Client extends MatrixApi {
|
|||
/// including all persistent data from the store.
|
||||
@override
|
||||
Future<void> logoutAll() async {
|
||||
try {
|
||||
await super.logoutAll();
|
||||
} catch (e, s) {
|
||||
final futures = <Future>[];
|
||||
futures.add(super.logoutAll());
|
||||
futures.add(clear());
|
||||
await Future.wait(futures).catchError((e, s) {
|
||||
Logs().e('Logout all failed', e, s);
|
||||
rethrow;
|
||||
} finally {
|
||||
await clear();
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
/// Run any request and react on user interactive authentication flows here.
|
||||
|
|
@ -816,6 +815,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<String?> 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<bool> 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<void> setAvatar(MatrixFile? file) async {
|
||||
|
|
|
|||
|
|
@ -310,4 +310,8 @@ abstract class DatabaseApi {
|
|||
Future<dynamic> close();
|
||||
|
||||
Future<T> transaction<T>(Future<T> Function() action);
|
||||
|
||||
Future<String> exportDump();
|
||||
|
||||
Future<bool> importDump(String export);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String> _clientBox;
|
||||
late Box<Map> _accountDataBox;
|
||||
late Box<Map> _roomsBox;
|
||||
|
|
@ -1458,4 +1458,108 @@ class FluffyBoxDatabase extends DatabaseApi {
|
|||
if (raw == null) return null;
|
||||
return raw;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> 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<bool> importDump(String export) async {
|
||||
try {
|
||||
await clear();
|
||||
await open();
|
||||
final json = Map.from(jsonDecode(export)).cast<String, Map>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1460,6 +1460,110 @@ class HiveCollectionsDatabase extends DatabaseApi {
|
|||
if (raw == null) return null;
|
||||
return raw;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> 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<bool> importDump(String export) async {
|
||||
try {
|
||||
await clear();
|
||||
await open();
|
||||
final json = Map.from(jsonDecode(export)).cast<String, Map>();
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -1405,6 +1405,18 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
|
|||
if (raw == null) return null;
|
||||
return raw as String;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> exportDump() {
|
||||
// see no need to implement this in a deprecated part
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> importDump(String export) {
|
||||
// see no need to implement this in a deprecated part
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
dynamic _castValue(dynamic value) {
|
||||
|
|
|
|||
|
|
@ -130,6 +130,10 @@ void test() async {
|
|||
.deviceKeys[testClientA.deviceID!]!.verified);
|
||||
assert(!testClientB.userDeviceKeys[TestUser.username]!
|
||||
.deviceKeys[testClientA.deviceID!]!.blocked);
|
||||
await Future.wait([
|
||||
testClientA.updateUserDeviceKeys(),
|
||||
testClientB.updateUserDeviceKeys(),
|
||||
]);
|
||||
await testClientA
|
||||
.userDeviceKeys[TestUser.username2]!.deviceKeys[testClientB.deviceID!]!
|
||||
.setVerified(true);
|
||||
|
|
|
|||
Loading…
Reference in New Issue