Merge branch 'session-export' into 'main'

feat: implement session export

See merge request famedly/company/frontend/famedlysdk!1019
This commit is contained in:
The one with the Braid 2022-05-23 12:46:55 +00:00
commit 2991fd9ca8
6 changed files with 284 additions and 8 deletions

View File

@ -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 {

View File

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

View File

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

View File

@ -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 {

View File

@ -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) {

View File

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