From 0697d47cc2791419ecf5dbc8840ac95bb6689e68 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 19 Nov 2020 12:12:15 +0100 Subject: [PATCH] refactor: Minor init refactoring --- lib/src/client.dart | 257 ++++++++++-------- test/client_test.dart | 21 +- .../encrypt_decrypt_to_device_test.dart | 7 +- test/encryption/key_verification_test.dart | 7 +- test/fake_client.dart | 6 +- test/fake_database.dart | 2 +- test/matrix_database_test.dart | 3 +- test_driver/famedlysdk_test.dart | 14 +- 8 files changed, 184 insertions(+), 133 deletions(-) diff --git a/lib/src/client.dart b/lib/src/client.dart index 033c8f04..8e0613a8 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -46,7 +46,9 @@ class Client extends MatrixApi { int _id; int get id => _id; - Database database; + final FutureOr Function(Client) databaseBuilder; + Database _database; + Database get database => _database; bool enableE2eeRecovery; @@ -87,7 +89,7 @@ class Client extends MatrixApi { /// in a room for the room list. Client( this.clientName, { - this.database, + this.databaseBuilder, this.enableE2eeRecovery = false, this.verificationMethods, http.Client httpClient, @@ -324,7 +326,7 @@ class Client extends MatrixApi { response.userId == null) { throw 'Registered but token, device ID or user ID is null.'; } - await connect( + await init( newToken: response.accessToken, newUserID: response.userId, newHomeserver: homeserver, @@ -369,7 +371,7 @@ class Client extends MatrixApi { loginResp.userId == null) { throw Exception('Registered but token, device ID or user ID is null.'); } - await connect( + await init( newToken: loginResp.accessToken, newUserID: loginResp.userId, newHomeserver: homeserver, @@ -587,120 +589,159 @@ class Client extends MatrixApi { /// an error? int syncErrorTimeoutSec = 3; - /// Sets the user credentials and starts the synchronisation. - /// - /// Before you can connect you need at least an [accessToken], a [homeserver], - /// a [userID], a [deviceID], and a [deviceName]. - /// - /// You get this informations - /// by logging in to your Matrix account, using the [login API](https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-login). - /// - /// To log in you can use [jsonRequest()] after you have set the [homeserver] - /// to a valid url. For example: - /// - /// ``` - /// final resp = await matrix - /// .jsonRequest(type: RequestType.POST, action: "/client/r0/login", data: { - /// "type": "m.login.password", - /// "user": "test", - /// "password": "1234", - /// "initial_device_display_name": "Matrix Client" - /// }); - /// ``` - /// - /// Returns: - /// - /// ``` - /// { - /// "user_id": "@cheeky_monkey:matrix.org", - /// "access_token": "abc123", - /// "device_id": "GHTYAJCE" - /// } - /// ``` - /// - /// Sends [LoginState.logged] to [onLoginStateChanged]. + @Deprecated('Use init() instead') void connect({ String newToken, Uri newHomeserver, String newUserID, String newDeviceName, String newDeviceID, - String newPrevBatch, + String newOlmAccount, + }) => + init( + newToken: newToken, + newHomeserver: newHomeserver, + newUserID: newUserID, + newDeviceName: newDeviceName, + newDeviceID: newDeviceID, + newOlmAccount: newOlmAccount, + ); + + bool _initLock = false; + + /// Sets the user credentials and starts the synchronisation. + /// + /// Before you can connect you need at least an [accessToken], a [homeserver], + /// a [userID], a [deviceID], and a [deviceName]. + /// + /// Usually you don't need to call this method by yourself because [login()], [register()] + /// and even the constructor calls it. + /// + /// Sends [LoginState.logged] to [onLoginStateChanged]. + /// + /// If one of [newToken], [newUserID], [newDeviceID], [newDeviceName] is set then + /// all of them must be set! If you don't set them, this method will try to + /// get them from the database. + void init({ + String newToken, + Uri newHomeserver, + String newUserID, + String newDeviceName, + String newDeviceID, String newOlmAccount, }) async { - String olmAccount; - if (database != null) { - final account = await database.getClient(clientName); - if (account != null) { - _id = account.clientId; - homeserver = Uri.parse(account.homeserverUrl); - accessToken = account.token; - _userID = account.userId; - _deviceID = account.deviceId; - _deviceName = account.deviceName; - prevBatch = account.prevBatch; - olmAccount = account.olmAccount; + if ((newToken != null || + newUserID != null || + newDeviceID != null || + newDeviceName != null) && + (newToken == null || + newUserID == null || + newDeviceID == null || + newDeviceName == null)) { + throw Exception( + 'If one of [newToken, newUserID, newDeviceID, newDeviceName] is set then all of them must be set!'); + } + + if (_initLock) throw Exception('[init()] has been called multiple times!'); + _initLock = true; + try { + Logs.info('Initialize client $clientName'); + if (isLogged()) { + throw Exception('User is already logged in! Call [logout()] first!'); } - } - accessToken = newToken ?? accessToken; - homeserver = newHomeserver ?? homeserver; - _userID = newUserID ?? _userID; - _deviceID = newDeviceID ?? _deviceID; - _deviceName = newDeviceName ?? _deviceName; - prevBatch = newPrevBatch ?? prevBatch; - olmAccount = newOlmAccount ?? olmAccount; - if (accessToken == null || homeserver == null || _userID == null) { - // we aren't logged in - encryption?.dispose(); - encryption = null; - onLoginStateChanged.add(LoginState.loggedOut); - return; - } + if (databaseBuilder != null) { + _database ??= await databaseBuilder(this); + } - encryption?.dispose(); - encryption = - Encryption(client: this, enableE2eeRecovery: enableE2eeRecovery); - await encryption.init(olmAccount); - - if (database != null) { - if (id != null) { - await database.updateClient( - homeserver.toString(), - accessToken, - _userID, - _deviceID, - _deviceName, - prevBatch, - encryption?.pickledOlmAccount, - id, - ); + String olmAccount; + if (database != null) { + final account = await database.getClient(clientName); + if (account != null) { + _id = account.clientId; + homeserver = Uri.parse(account.homeserverUrl); + accessToken = account.token; + _userID = account.userId; + _deviceID = account.deviceId; + _deviceName = account.deviceName; + prevBatch = account.prevBatch; + olmAccount = account.olmAccount; + } + } + if (newToken != null) { + accessToken = newToken; + homeserver = newHomeserver; + _userID = newUserID; + _deviceID = newDeviceID; + _deviceName = newDeviceName; + olmAccount = newOlmAccount; } else { - _id = await database.insertClient( - clientName, - homeserver.toString(), - accessToken, - _userID, - _deviceID, - _deviceName, - prevBatch, - encryption?.pickledOlmAccount, - ); + accessToken = newToken ?? accessToken; + homeserver = newHomeserver ?? homeserver; + _userID = newUserID ?? _userID; + _deviceID = newDeviceID ?? _deviceID; + _deviceName = newDeviceName ?? _deviceName; + olmAccount = newOlmAccount ?? olmAccount; } - _userDeviceKeys = await database.getUserDeviceKeys(this); - _rooms = await database.getRoomList(this, onlyLeft: false); - _sortRooms(); - accountData = await database.getAccountData(id); - presences.clear(); + + if (accessToken == null || homeserver == null || _userID == null) { + // we aren't logged in + encryption?.dispose(); + encryption = null; + onLoginStateChanged.add(LoginState.loggedOut); + Logs.info('User is not logged in.'); + _initLock = false; + return; + } + _initLock = false; + + encryption?.dispose(); + encryption = + Encryption(client: this, enableE2eeRecovery: enableE2eeRecovery); + await encryption.init(olmAccount); + + if (database != null) { + if (id != null) { + await database.updateClient( + homeserver.toString(), + accessToken, + _userID, + _deviceID, + _deviceName, + prevBatch, + encryption?.pickledOlmAccount, + id, + ); + } else { + _id = await database.insertClient( + clientName, + homeserver.toString(), + accessToken, + _userID, + _deviceID, + _deviceName, + prevBatch, + encryption?.pickledOlmAccount, + ); + } + _userDeviceKeys = await database.getUserDeviceKeys(this); + _rooms = await database.getRoomList(this, onlyLeft: false); + _sortRooms(); + accountData = await database.getAccountData(id); + presences.clear(); + } + + onLoginStateChanged.add(LoginState.logged); + Logs.success( + 'Successfully connected as ${userID.localpart} with ${homeserver.toString()}', + ); + return _sync(); + } catch (e, s) { + Logs.error('Initialization failed: ${e.toString()}', s); + clear(); + onLoginStateChanged.addError(e, s); + rethrow; } - - onLoginStateChanged.add(LoginState.logged); - Logs.success( - 'Successfully connected as ${userID.localpart} with ${homeserver.toString()}', - ); - - // Always do a _sync after login, even if backgroundSync is set to off - return _sync(); } /// Used for testing only @@ -1629,7 +1670,7 @@ sort order of ${prevState.sortOrder}. This should never happen...'''); /// Stops the synchronization and closes the database. After this /// you can safely make this Client instance null. - Future dispose({bool closeDatabase = false}) async { + Future dispose({bool closeDatabase = true}) async { _disposed = true; try { await _currentTransaction; @@ -1639,11 +1680,13 @@ sort order of ${prevState.sortOrder}. This should never happen...'''); encryption?.dispose(); encryption = null; try { - if (closeDatabase) await database?.close(); + if (closeDatabase && database != null) { + await database.close().catchError((e, s) => Logs.warning(e, s)); + _database = null; + } } catch (error, stacktrace) { Logs.warning('Failed to close database: ' + error.toString(), stacktrace); } - database = null; return; } } diff --git a/test/client_test.dart b/test/client_test.dart index b554f774..ad6f3c70 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -91,7 +91,7 @@ void main() { var firstSyncFuture = matrix.onFirstSync.stream.first; var syncFuture = matrix.onSync.stream.first; - matrix.connect( + matrix.init( newToken: 'abcd', newUserID: '@test:fakeServer.notExisting', newHomeserver: matrix.homeserver, @@ -379,10 +379,14 @@ void main() { }); }); test('Test the fake store api', () async { - var client1 = Client('testclient', httpClient: FakeMatrixApi()); - client1.database = getDatabase(); + final database = await getDatabase(null); + var client1 = Client( + 'testclient', + httpClient: FakeMatrixApi(), + databaseBuilder: (_) => database, + ); - client1.connect( + client1.init( newToken: 'abc123', newUserID: '@test:fakeServer.notExisting', newHomeserver: Uri.parse('https://fakeServer.notExisting'), @@ -396,10 +400,13 @@ void main() { expect(client1.isLogged(), true); expect(client1.rooms.length, 2); - var client2 = Client('testclient', httpClient: FakeMatrixApi()); - client2.database = client1.database; + var client2 = Client( + 'testclient', + httpClient: FakeMatrixApi(), + databaseBuilder: (_) => database, + ); - client2.connect(); + client2.init(); await Future.delayed(Duration(milliseconds: 100)); expect(client2.isLogged(), true); diff --git a/test/encryption/encrypt_decrypt_to_device_test.dart b/test/encryption/encrypt_decrypt_to_device_test.dart index 3a95f750..45c6122c 100644 --- a/test/encryption/encrypt_decrypt_to_device_test.dart +++ b/test/encryption/encrypt_decrypt_to_device_test.dart @@ -22,6 +22,7 @@ import 'package:test/test.dart'; import 'package:olm/olm.dart' as olm; import '../fake_client.dart'; +import '../fake_database.dart'; import '../fake_matrix_api.dart'; void main() { @@ -43,15 +44,15 @@ void main() { if (!olmEnabled) return; Client client; - var otherClient = Client('othertestclient', httpClient: FakeMatrixApi()); + var otherClient = Client('othertestclient', + httpClient: FakeMatrixApi(), databaseBuilder: getDatabase); DeviceKeys device; Map payload; test('setupClient', () async { client = await getClient(); - otherClient.database = client.database; await otherClient.checkHomeserver('https://fakeServer.notExisting'); - otherClient.connect( + otherClient.init( newToken: 'abc', newUserID: '@othertest:fakeServer.notExisting', newHomeserver: otherClient.homeserver, diff --git a/test/encryption/key_verification_test.dart b/test/encryption/key_verification_test.dart index 27db626c..65646aac 100644 --- a/test/encryption/key_verification_test.dart +++ b/test/encryption/key_verification_test.dart @@ -83,10 +83,11 @@ void main() { test('setupClient', () async { client1 = await getClient(); - client2 = Client('othertestclient', httpClient: FakeMatrixApi()); - client2.database = client1.database; + client2 = Client('othertestclient', + httpClient: FakeMatrixApi(), + databaseBuilder: (_) => client1.database); await client2.checkHomeserver('https://fakeServer.notExisting'); - client2.connect( + client2.init( newToken: 'abc', newUserID: '@othertest:fakeServer.notExisting', newHomeserver: client2.homeserver, diff --git a/test/fake_client.dart b/test/fake_client.dart index bcc7f18d..8859adf8 100644 --- a/test/fake_client.dart +++ b/test/fake_client.dart @@ -29,10 +29,10 @@ const pickledOlmAccount = 'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuyg3RxViwdNxs3718fyAqQ/VSwbXsY0Nl+qQbF+nlVGHenGqk5SuNl1P6e1PzZxcR0IfXA94Xij1Ob5gDv5YH4UCn9wRMG0abZsQP0YzpDM0FLaHSCyo9i5JD/vMlhH+nZWrgAzPPCTNGYewNV8/h3c+VyJh8ZTx/fVi6Yq46Fv+27Ga2ETRZ3Qn+Oyx6dLBjnBZ9iUvIhqpe2XqaGA1PopOz8iDnaZitw'; Future getClient() async { - final client = Client('testclient', httpClient: FakeMatrixApi()); - client.database = getDatabase(); + final client = Client('testclient', + httpClient: FakeMatrixApi(), databaseBuilder: getDatabase); await client.checkHomeserver('https://fakeServer.notExisting'); - client.connect( + client.init( newToken: 'abcd', newUserID: '@test:fakeServer.notExisting', newHomeserver: client.homeserver, diff --git a/test/fake_database.dart b/test/fake_database.dart index d270c802..e8b60207 100644 --- a/test/fake_database.dart +++ b/test/fake_database.dart @@ -20,7 +20,7 @@ import 'package:famedlysdk/famedlysdk.dart'; import 'package:moor/moor.dart'; import 'package:moor/ffi.dart' as moor; -Database getDatabase() { +Future getDatabase(Client _) async { moorRuntimeOptions.dontWarnAboutMultipleDatabases = true; return Database(moor.VmDatabase.memory()); } diff --git a/test/matrix_database_test.dart b/test/matrix_database_test.dart index f41eebe3..ef02b138 100644 --- a/test/matrix_database_test.dart +++ b/test/matrix_database_test.dart @@ -23,10 +23,10 @@ import 'fake_database.dart'; void main() { group('Databse', () { - final database = getDatabase(); var clientId = -1; var room = Room(id: '!room:blubb'); test('setupDatabase', () async { + final database = await getDatabase(null); clientId = await database.insertClient( 'testclient', 'https://example.org', @@ -38,6 +38,7 @@ void main() { null); }); test('storeEventUpdate', () async { + final database = await getDatabase(null); // store a simple update var update = EventUpdate( type: EventUpdateType.timeline, diff --git a/test_driver/famedlysdk_test.dart b/test_driver/famedlysdk_test.dart index 81300e45..d70d46b2 100644 --- a/test_driver/famedlysdk_test.dart +++ b/test_driver/famedlysdk_test.dart @@ -22,16 +22,14 @@ void test() async { Logs.success('[LibOlm] Enabled'); Logs.success('++++ Login Alice at ++++'); - testClientA = Client('TestClientA'); - testClientA.database = getDatabase(); + testClientA = Client('TestClientA', databaseBuilder: getDatabase); await testClientA.checkHomeserver(TestUser.homeserver); await testClientA.login( user: TestUser.username, password: TestUser.password); assert(testClientA.encryptionEnabled); Logs.success('++++ Login Bob ++++'); - testClientB = Client('TestClientB'); - testClientB.database = getDatabase(); + testClientB = Client('TestClientB', databaseBuilder: getDatabase); await testClientB.checkHomeserver(TestUser.homeserver); await testClientB.login( user: TestUser.username2, password: TestUser.password); @@ -221,7 +219,7 @@ void test() async { "++++ (Alice) Received decrypted message: '${room.lastMessage}' ++++"); Logs.success('++++ Login Bob in another client ++++'); - var testClientC = Client('TestClientC', database: getDatabase()); + var testClientC = Client('TestClientC', databaseBuilder: getDatabase); await testClientC.checkHomeserver(TestUser.homeserver); await testClientC.login( user: TestUser.username2, password: TestUser.password); @@ -269,7 +267,7 @@ void test() async { "++++ (Bob) Received decrypted message: '${inviteRoom.lastMessage}' ++++"); Logs.success('++++ Logout Bob another client ++++'); - await testClientC.dispose(); + await testClientC.dispose(closeDatabase: false); await testClientC.logout(); testClientC = null; await Future.delayed(Duration(seconds: 5)); @@ -317,8 +315,8 @@ void test() async { Logs.success('++++ Logout Alice and Bob ++++'); if (testClientA?.isLogged() ?? false) await testClientA.logoutAll(); if (testClientA?.isLogged() ?? false) await testClientB.logoutAll(); - await testClientA?.dispose(); - await testClientB?.dispose(); + await testClientA?.dispose(closeDatabase: false); + await testClientB?.dispose(closeDatabase: false); testClientA = null; testClientB = null; }