Index: src/MTXClient.h ================================================================== --- src/MTXClient.h +++ src/MTXClient.h @@ -42,10 +42,17 @@ * * @param exception `nil` on success, otherwise an exception */ typedef void (^mtx_client_response_block_t)(id _Nullable exception); +/** + * @brief A block called when an exception occurred during sync. + * + * @param exception The exception which occurred during sync + */ +typedef void (^mtx_sync_exception_handler_block_t)(id exception); + /** * @brief A block called when the room list was fetched. * * @param rooms An array of joined rooms, or nil on error * @param exception An exception if fetching the room list failed @@ -90,10 +97,23 @@ /** * @brief The storage used by the client. */ @property (readonly, nonatomic) id storage; +/** + * @brief The timeout for sync requests. + * + * Defaults to 5 minutes. + */ +@property (nonatomic) of_time_interval_t syncTimeout; + +/** + * @brief A block to handle exceptions that occurred during sync. + */ +@property (copy, nonatomic) + mtx_sync_exception_handler_block_t syncExceptionHandler; + /** * @brief Creates a new client with the specified access token on the specified * homeserver. * * @param userID The user ID for the client @@ -141,16 +161,21 @@ homeserver: (OFURL *)homeserver storage: (id )storage OF_DESIGNATED_INITIALIZER; /** - * @brief Performs a sync. + * @brief Starts the sync loop. + */ +- (void)startSyncLoop; + +/** + * @brief Stops the sync loop. * - * @param block A block to call when a sync was performed + * The currently waiting sync is not aborted, but after it returns, no new sync + * will be started. */ -- (void)syncWithTimeout: (of_time_interval_t)timeout - block: (mtx_client_response_block_t)block; +- (void)stopSyncLoop; /** * @brief Logs out the device and invalidates the access token. * * @warning The client can no longer be used after this succeeded! Index: src/MTXClient.m ================================================================== --- src/MTXClient.m +++ src/MTXClient.m @@ -46,10 +46,14 @@ homeserver.query != nil || homeserver.fragment != nil) @throw [OFInvalidArgumentException exception]; } @implementation MTXClient +{ + bool _syncing; +} + + (instancetype)clientWithUserID: (OFString *)userID deviceID: (OFString *)deviceID accessToken: (OFString *)accessToken homeserver: (OFURL *)homeserver storage: (id )storage @@ -156,10 +160,11 @@ _userID = [userID copy]; _deviceID = [deviceID copy]; _accessToken = [accessToken copy]; _homeserver = [homeserver copy]; _storage = [storage retain]; + _syncTimeout = 300; } @catch (id e) { [self release]; @throw e; } @@ -194,40 +199,48 @@ return [MTXRequest requestWithPath: path accessToken: _accessToken homeserver: _homeserver]; } -- (void)syncWithTimeout: (of_time_interval_t)timeout - block: (mtx_client_response_block_t)block +- (void)startSyncLoop { + if (_syncing) + return; + + _syncing = true; + void *pool = objc_autoreleasePoolPush(); MTXRequest *request = [self requestWithPath: @"/_matrix/client/r0/sync"]; - unsigned long long timeoutMs = timeout * 1000; + unsigned long long timeoutMs = _syncTimeout * 1000; OFMutableDictionary *query = [OFMutableDictionary dictionaryWithObject: @(timeoutMs).stringValue forKey: @"timeout"]; query[@"since"] = [_storage nextBatchForDeviceID: _deviceID]; request.query = query; [request performWithBlock: ^ (mtx_response_t response, int statusCode, id exception) { if (exception != nil) { - block(exception); + if (_syncExceptionHandler != NULL) + _syncExceptionHandler(exception); return; } if (statusCode != 200) { - block([MTXSyncFailedException - exceptionWithStatusCode: statusCode - response: response - client: self]); + if (_syncExceptionHandler != NULL) + _syncExceptionHandler([MTXSyncFailedException + exceptionWithStatusCode: statusCode + response: response + client: self]); return; } OFString *nextBatch = response[@"next_batch"]; if (![nextBatch isKindOfClass: OFString.class]) { - block([OFInvalidServerReplyException exception]); + if (_syncExceptionHandler != NULL) + _syncExceptionHandler( + [OFInvalidServerReplyException exception]); return; } @try { [_storage transactionWithBlock: ^ { @@ -243,19 +256,26 @@ response[@"to_device"]]; return true; }]; } @catch (id e) { - block(e); + if (_syncExceptionHandler != NULL) + _syncExceptionHandler(e); return; } - block(nil); + if (_syncing) + [self startSyncLoop]; }]; objc_autoreleasePoolPop(pool); } + +- (void)stopSyncLoop +{ + _syncing = false; +} - (void)logOutWithBlock: (mtx_client_response_block_t)block { void *pool = objc_autoreleasePoolPush(); MTXRequest *request = Index: tests/tests.m ================================================================== --- tests/tests.m +++ tests/tests.m @@ -61,25 +61,11 @@ } _client = [client retain]; of_log(@"Logged in client: %@", _client); - [self sync]; - }]; -} - -- (void)sync -{ - [_client syncWithTimeout: 5 - block: ^ (id exception) { - if (exception != nil) { - of_log(@"Failed to sync: %@", exception); - [OFApplication terminateWithStatus: 1]; - } - - of_log(@"Synced"); - + [_client startSyncLoop]; [self fetchRoomList]; }]; } - (void)fetchRoomList @@ -108,25 +94,10 @@ } _roomID = [roomID copy]; of_log(@"Joined room %@", _roomID); - [self sync2]; - }]; -} - -- (void)sync2 -{ - [_client syncWithTimeout: 5 - block: ^ (id exception) { - if (exception != nil) { - of_log(@"Failed to sync: %@", exception); - [OFApplication terminateWithStatus: 1]; - } - - of_log(@"Synced"); - [self sendMessage]; }]; } - (void)sendMessage @@ -140,11 +111,15 @@ [OFApplication terminateWithStatus: 1]; } of_log(@"Message sent to %@", _roomID); - [self leaveRoom]; + of_log( + @"Waiting 5 seconds before leaving room and logging out"); + + [self performSelector: @selector(leaveRoom) + afterDelay: 5]; }]; } - (void)leaveRoom {