Skip to content

Commit

Permalink
Merge pull request #1666 from matrix-org/gil/6059-Load_thread_list_us…
Browse files Browse the repository at this point in the history
…ing_server-side_sorting_and_pagination

Load the thread list using server-side sorting and pagination
  • Loading branch information
gileluard authored Dec 28, 2022
2 parents fec5cd6 + 1c847ae commit 8ae45d2
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 24 deletions.
37 changes: 37 additions & 0 deletions MatrixSDK/Contrib/Swift/MXRestClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,27 @@ public enum MXAccountDataType: Equatable, Hashable {
}
}

/// Represents the threads request include parameter
public enum MXThreadsIncludeParameter: String, RawRepresentable, Equatable, Hashable {
case all
case participated

public init?(rawValue: String) {
switch rawValue {
case kMXThreadsListIncludeAllParameter: self = .all
case kMXThreadsListIncludeParticipatedParameter: self = .participated
default:
return nil
}
}

public var rawValue: String {
switch self {
case .all: return kMXThreadsListIncludeAllParameter
case .participated: return kMXThreadsListIncludeParticipatedParameter
}
}
}



Expand Down Expand Up @@ -1240,6 +1261,22 @@ public extension MXRestClient {
return __roomSummary(with: roomIdOrAlias, via: via, success: currySuccess(completion), failure: curryFailure(completion))
}

/**
List all the threads of a room.

- parameters:
- roomId: the id of the room.
- include: wether the response should include all threads (e.g. `.all`) or only threads participated by the user (e.g. `.participated`)
- from: the token to pass for doing pagination from a previous response.
- completion: A block object called when the operation completes.
- response: Provides the list of root events of the threads and, optionally, the next batch token.

- returns: a `MXHTTPOperation` instance.
*/
@nonobjc @discardableResult func threadsInRoomWithId(_ roomId: String, include: MXThreadsIncludeParameter, from: String?, completion: @escaping (_ response: MXResponse<MXAggregationPaginatedResponse>) -> Void) -> MXHTTPOperation {
return __threadsInRoom(withId: roomId, include: include.rawValue, from: from, success: currySuccess(completion), failure: curryFailure(completion))
};

/**
Upgrade a room to a new version

Expand Down
22 changes: 22 additions & 0 deletions MatrixSDK/MXRestClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ FOUNDATION_EXPORT NSString *const kMXAccountDataTypeRecentRoomsKey;
FOUNDATION_EXPORT NSString *const kMXAccountDataLocalNotificationKeyPrefix;
FOUNDATION_EXPORT NSString *const kMXAccountDataIsSilencedKey;

/**
Threads list request parameters
*/
FOUNDATION_EXPORT NSString *const kMXThreadsListIncludeAllParameter;
FOUNDATION_EXPORT NSString *const kMXThreadsListIncludeParticipatedParameter;

/**
MXRestClient error domain
Expand Down Expand Up @@ -1700,6 +1705,23 @@ NS_REFINED_FOR_SWIFT;
success:(void (^)(NSString *replacementRoomId))success
failure:(void (^)(NSError *error))failure NS_REFINED_FOR_SWIFT;

/**
List all the threads of a room.
@param roomId the id of the room.
@param include wether the response should include all threads (e.g. `kMXThreadsListIncludeAllParameter`) or only threads participated by the user (e.g. `kMXThreadsListIncludeParticipatedParameter`)
@param from the token to pass for doing pagination from a previous response.
@param success A block object called when the operation succeeds. It provides the list of root events of the threads and, optionally, the next batch token.
@param failure A block object called when the operation fails.
@return a MXHTTPOperation instance.
*/
- (MXHTTPOperation*)threadsInRoomWithId:(NSString*)roomId
include:(NSString *)include
from:(nullable NSString*)from
success:(void (^)(MXAggregationPaginatedResponse *response))success
failure:(void (^)(NSError *error))failure NS_REFINED_FOR_SWIFT;

#pragma mark - Room tags operations
/**
List the tags of a room.
Expand Down
36 changes: 36 additions & 0 deletions MatrixSDK/MXRestClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@

NSString *const MXCredentialsUpdateTokensNotification = @"MXCredentialsUpdateTokensNotification";
NSString *const kMXCredentialsNewRefreshTokenDataKey = @"refresh_token_data";

/**
Threads list request parameters
*/
NSString *const kMXThreadsListIncludeAllParameter = @"all";
NSString *const kMXThreadsListIncludeParticipatedParameter = @"participated";

/**
The time interval before the access token expires that we will start trying to refresh the token.
This avoids us having to block other users requests while the token refreshes.
Expand Down Expand Up @@ -3330,6 +3337,35 @@ - (MXHTTPOperation*)upgradeRoomWithId:(NSString*)roomId
}];
}

- (MXHTTPOperation*)threadsInRoomWithId:(NSString*)roomId
include:(NSString *)include
from:(nullable NSString*)from
success:(void (^)(MXAggregationPaginatedResponse *response))success
failure:(void (^)(NSError *error))failure
{
NSString *path = [NSString stringWithFormat:@"%@/rooms/%@/threads", kMXAPIPrefixPathV1, [MXTools encodeURIComponent:roomId]];

NSDictionary *parameters = from ? @{@"include": include, @"from": from} : @{@"include": include};

MXWeakify(self);
return [httpClient requestWithMethod:@"GET"
path:path
parameters:parameters
success:^(NSDictionary *JSONResponse) {
MXStrongifyAndReturnIfNil(self);
__block MXAggregationPaginatedResponse *response;
[self dispatchProcessing:^{
MXJSONModelSetMXJSONModel(response, MXAggregationPaginatedResponse, JSONResponse);
} andCompletion:^{
success(response);
}];
}
failure:^(NSError *error) {
MXStrongifyAndReturnIfNil(self);
[self dispatchFailure:error inBlock:failure];
}];
}

#pragma mark - Room tags operations
- (MXHTTPOperation*)tagsOfRoom:(NSString*)roomId
success:(void (^)(NSArray<MXRoomTag*> *tags))success
Expand Down
92 changes: 71 additions & 21 deletions MatrixSDK/Threads/MXThreadingService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ public enum MXThreadingServiceError: Int, Error {
case unknown
}

/// MXThreadingService allThreads response
public struct MXThreadingServiceResponse {
public let threads: [MXThreadProtocol]
public let nextBatch: String?
}

// MARK: - MXThreadingService errors
extension MXThreadingServiceError: CustomNSError {
public static let errorDomain = "org.matrix.sdk.threadingservice"
Expand Down Expand Up @@ -190,11 +196,12 @@ public class MXThreadingService: NSObject {
}
}
}

@discardableResult
public func allThreads(inRoom roomId: String,
onlyParticipated: Bool = false,
completion: @escaping (MXResponse<[MXThreadProtocol]>) -> Void) -> MXHTTPOperation? {
from: String?,
onlyParticipated: Bool,
completion: @escaping (MXResponse<MXThreadingServiceResponse>) -> Void) -> MXHTTPOperation? {
guard let session = session else {
DispatchQueue.main.async {
completion(.failure(MXThreadingServiceError.sessionNotFound))
Expand All @@ -218,23 +225,10 @@ public class MXThreadingService: NSObject {

dispatchGroup.notify(queue: .main) {
if serverSupportThreads {
// homeserver supports threads
let filter = MXRoomEventFilter()
filter.relatedByTypes = [MXEventRelationTypeThread]
if onlyParticipated {
filter.relatedBySenders = [session.myUserId]
}
let newOperation = session.matrixRestClient.messages(forRoom: roomId,
from: "",
direction: .backwards,
limit: nil,
filter: filter) { response in
let newOperation = session.matrixRestClient.threadsInRoomWithId(roomId, include: onlyParticipated ? .participated : .all, from: from) { response in
switch response {
case .success(let paginationResponse):
guard let rootEvents = paginationResponse.chunk else {
completion(.success([]))
return
}
let rootEvents = paginationResponse.chunk

session.decryptEvents(rootEvents, inTimeline: nil) { _ in
let threads = rootEvents.map { self.thread(forRootEvent: $0, session: session) }.sorted(by: <)
Expand Down Expand Up @@ -273,9 +267,10 @@ public class MXThreadingService: NSObject {
}

decryptionGroup.notify(queue: .main) {
completion(.success(threads))
completion(.success(MXThreadingServiceResponse(threads: threads, nextBatch: paginationResponse.nextBatch)))
}
}

case .failure(let error):
completion(.failure(error))
}
Expand All @@ -285,16 +280,41 @@ public class MXThreadingService: NSObject {
} else {
// use local implementation
if onlyParticipated {
completion(.success(self.localParticipatedThreads(inRoom: roomId)))
completion(.success(MXThreadingServiceResponse(threads: self.localParticipatedThreads(inRoom: roomId), nextBatch: nil)))
} else {
completion(.success(self.localThreads(inRoom: roomId)))
completion(.success(MXThreadingServiceResponse(threads: self.localThreads(inRoom: roomId), nextBatch: nil)))
}
}
}

return operation
}

@discardableResult
public func allThreads(inRoom roomId: String,
onlyParticipated: Bool = false,
completion: @escaping (MXResponse<[MXThreadProtocol]>) -> Void) -> MXHTTPOperation? {
var operation: MXHTTPOperation? = nil
operation = allThreads(inRoom: roomId, from: nil, onlyParticipated: onlyParticipated) { response in
guard let mainOperation = operation else {
return
}

switch response {
case .success(let value):
if let nextBatch = value.nextBatch {
self.allThreads(inRoom: roomId, from: nextBatch, operation: mainOperation, onlyParticipated: onlyParticipated, threads: value.threads, completion: completion)
} else {
completion(.success(value.threads))
}
case .failure(let error):
completion(.failure(error))
}
}

return operation
}

// MARK: - Private

private func localThreads(inRoom roomId: String) -> [MXThreadProtocol] {
Expand Down Expand Up @@ -457,6 +477,36 @@ public class MXThreadingService: NSObject {
threads[thread.id] = thread
}

// This method calls recursively the `allThreads` method until no next batch token is returned by the server
// in order to aggregate all the threads of a room.
private func allThreads(inRoom roomId: String,
from: String?,
operation: MXHTTPOperation,
onlyParticipated: Bool,
threads: [MXThreadProtocol],
completion: @escaping (MXResponse<[MXThreadProtocol]>) -> Void) {
var newOperation: MXHTTPOperation? = nil
newOperation = allThreads(inRoom: roomId, from: from, onlyParticipated: onlyParticipated, completion: { response in
switch response {
case .success(let value):
let threads = threads + value.threads
if let nextBatch = value.nextBatch {
self.allThreads(inRoom: roomId, from: nextBatch, operation: operation, onlyParticipated: onlyParticipated, threads: threads, completion: completion)
} else {
completion(.success(threads))
}
case .failure(let error):
completion(.failure(error))
}
})

guard let currentOperation = newOperation else {
return
}

operation.mutate(to: currentOperation)
}

// MARK: - Delegate

/// Add delegate instance
Expand Down
4 changes: 2 additions & 2 deletions MatrixSDKTests/MXFilterTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,8 @@ - (void)testUnsupportedSyncFilter

[mxSession startWithSyncFilter:badFilter onServerSyncDone:^{

XCTAssertNil(mxSession.syncFilterId);

// https://github.com/matrix-org/synapse/pull/14369
XCTAssertTrue([mxSession.syncFilterId isEqualToString:@"0"]);
[expectation fulfill];

} failure:^(NSError *error) {
Expand Down
2 changes: 1 addition & 1 deletion MatrixSDKTests/MXSpaceServiceTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ class MXSpaceServiceTest: XCTestCase {
return
}

XCTAssert(roomState.powerLevels.eventsDefault == 100)
XCTAssertNil(roomState.powerLevels)

expectation.fulfill()
}
Expand Down
1 change: 1 addition & 0 deletions changelog.d/6059.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Threads: Load the thread list using server-side sorting and pagination

0 comments on commit 8ae45d2

Please sign in to comment.