From 8cc9caa0fda30ec788366db2673f99f3df7cb324 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 15 Feb 2022 16:44:10 +1100 Subject: [PATCH] Renamed the OpenGroupPollerV2 and OpenGroupManagerV2 --- Session.xcodeproj/project.pbxproj | 16 +- .../Views & Modals/JoinOpenGroupModal.swift | 23 +- Session/Home/HomeVC.swift | 2 +- Session/Meta/AppDelegate.m | 4 +- Session/Open Groups/JoinOpenGroupVC.swift | 4 +- Session/Utilities/BackgroundPoller.swift | 74 ++--- .../Open Groups/OpenGroupAPI.swift | 8 +- ...ManagerV2.swift => OpenGroupManager.swift} | 53 ++-- .../MessageReceiver+Handling.swift | 4 +- .../Pollers/OpenGroupPoller.swift | 252 ++++++++++++++++++ .../Pollers/OpenGroupPollerV2.swift | 204 -------------- 11 files changed, 356 insertions(+), 288 deletions(-) rename SessionMessagingKit/Open Groups/{OpenGroupManagerV2.swift => OpenGroupManager.swift} (89%) create mode 100644 SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift delete mode 100644 SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index f1ff64623..b671fabb6 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -752,8 +752,8 @@ C3D9E52725677DF20040E4F3 /* OWSThumbnailService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAF1255A580500E217F9 /* OWSThumbnailService.swift */; }; C3DA9C0725AE7396008F7C7E /* ConfigurationMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DA9C0625AE7396008F7C7E /* ConfigurationMessage.swift */; }; C3DAB3242480CB2B00725F25 /* SRCopyableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */; }; - C3DB66AC260ACA42001EFC55 /* OpenGroupManagerV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DB66AB260ACA42001EFC55 /* OpenGroupManagerV2.swift */; }; - C3DB66C3260ACCE6001EFC55 /* OpenGroupPollerV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DB66C2260ACCE6001EFC55 /* OpenGroupPollerV2.swift */; }; + C3DB66AC260ACA42001EFC55 /* OpenGroupManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DB66AB260ACA42001EFC55 /* OpenGroupManager.swift */; }; + C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DB66C2260ACCE6001EFC55 /* OpenGroupPoller.swift */; }; C3DB66CC260AF1F3001EFC55 /* OpenGroupAPI+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DB66CB260AF1F3001EFC55 /* OpenGroupAPI+ObjC.swift */; }; C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */; }; C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */; }; @@ -1859,8 +1859,8 @@ C3D9E43025676D3D0040E4F3 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; C3DA9C0625AE7396008F7C7E /* ConfigurationMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationMessage.swift; sourceTree = ""; }; C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRCopyableLabel.swift; sourceTree = ""; }; - C3DB66AB260ACA42001EFC55 /* OpenGroupManagerV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupManagerV2.swift; sourceTree = ""; }; - C3DB66C2260ACCE6001EFC55 /* OpenGroupPollerV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupPollerV2.swift; sourceTree = ""; }; + C3DB66AB260ACA42001EFC55 /* OpenGroupManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupManager.swift; sourceTree = ""; }; + C3DB66C2260ACCE6001EFC55 /* OpenGroupPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupPoller.swift; sourceTree = ""; }; C3DB66CB260AF1F3001EFC55 /* OpenGroupAPI+ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenGroupAPI+ObjC.swift"; sourceTree = ""; }; C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sheet.swift; sourceTree = ""; }; C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditClosedGroupVC.swift; sourceTree = ""; }; @@ -2748,7 +2748,7 @@ isa = PBXGroup; children = ( C33FDB34255A580B00E217F9 /* ClosedGroupPoller.swift */, - C3DB66C2260ACCE6001EFC55 /* OpenGroupPollerV2.swift */, + C3DB66C2260ACCE6001EFC55 /* OpenGroupPoller.swift */, C33FDB3A255A580B00E217F9 /* Poller.swift */, ); path = Pollers; @@ -3357,7 +3357,7 @@ FDC4380727B31D3A00C60D73 /* Types */, B88FA7B726045D100049422F /* OpenGroupAPI.swift */, C3DB66CB260AF1F3001EFC55 /* OpenGroupAPI+ObjC.swift */, - C3DB66AB260ACA42001EFC55 /* OpenGroupManagerV2.swift */, + C3DB66AB260ACA42001EFC55 /* OpenGroupManager.swift */, ); path = "Open Groups"; sourceTree = ""; @@ -5131,7 +5131,7 @@ C3D9E52725677DF20040E4F3 /* OWSThumbnailService.swift in Sources */, C32C5E75256DE020003C73A2 /* YapDatabaseTransaction+OWS.m in Sources */, C3BBE0802554CDD70050F1E3 /* Storage.swift in Sources */, - C3DB66AC260ACA42001EFC55 /* OpenGroupManagerV2.swift in Sources */, + C3DB66AC260ACA42001EFC55 /* OpenGroupManager.swift in Sources */, B8F5F61B25EDE4BF003BF8D4 /* DataExtractionNotificationInfoMessage.swift in Sources */, FDC438A427BB107F00C60D73 /* UserBanRequest.swift in Sources */, C379DCF4256735770002D4EB /* VisibleMessage+Attachment.swift in Sources */, @@ -5230,7 +5230,7 @@ C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */, FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */, B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */, - C3DB66C3260ACCE6001EFC55 /* OpenGroupPollerV2.swift in Sources */, + C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */, FDC438AE27BB148700C60D73 /* UserDeleteMessagesResponse.swift in Sources */, C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */, C32C5FBB256E0206003C73A2 /* OWSBackgroundTask.m in Sources */, diff --git a/Session/Conversations/Views & Modals/JoinOpenGroupModal.swift b/Session/Conversations/Views & Modals/JoinOpenGroupModal.swift index d6a47a02b..6b51dc2e6 100644 --- a/Session/Conversations/Views & Modals/JoinOpenGroupModal.swift +++ b/Session/Conversations/Views & Modals/JoinOpenGroupModal.swift @@ -63,23 +63,24 @@ final class JoinOpenGroupModal : Modal { // MARK: Interaction @objc private func joinOpenGroup() { - guard let (room, server, publicKey) = OpenGroupManagerV2.parseV2OpenGroup(from: url) else { + guard let (room, server, publicKey) = OpenGroupManager.parseV2OpenGroup(from: url) else { let alert = UIAlertController(title: "Couldn't Join", message: nil, preferredStyle: .alert) alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil)) return presentingViewController!.present(alert, animated: true, completion: nil) } presentingViewController!.dismiss(animated: true, completion: nil) Storage.shared.write { [presentingViewController = self.presentingViewController!] transaction in - OpenGroupManagerV2.shared.add(room: room, server: server, publicKey: publicKey, using: transaction) - .done(on: DispatchQueue.main) { _ in - let appDelegate = UIApplication.shared.delegate as! AppDelegate - appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...) - } - .catch(on: DispatchQueue.main) { error in - let alert = UIAlertController(title: "Couldn't Join", message: error.localizedDescription, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil)) - presentingViewController.present(alert, animated: true, completion: nil) - } + OpenGroupManager.shared + .add(room: room, server: server, publicKey: publicKey, using: transaction) + .done(on: DispatchQueue.main) { _ in + let appDelegate = UIApplication.shared.delegate as! AppDelegate + appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...) + } + .catch(on: DispatchQueue.main) { error in + let alert = UIAlertController(title: "Couldn't Join", message: error.localizedDescription, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil)) + presentingViewController.present(alert, animated: true, completion: nil) + } } } } diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index d0a15cae3..15db7dccc 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -525,7 +525,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv Storage.write { transaction in Storage.shared.cancelPendingMessageSendJobs(for: thread.uniqueId!, using: transaction) if let openGroupV2 = openGroupV2 { - OpenGroupManagerV2.shared.delete(openGroupV2, associatedWith: thread, using: transaction) + OpenGroupManager.shared.delete(openGroupV2, associatedWith: thread, using: transaction) } else if let thread = thread as? TSGroupThread, thread.isClosedGroup == true { let groupID = thread.groupModel.groupId let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID) diff --git a/Session/Meta/AppDelegate.m b/Session/Meta/AppDelegate.m index 0bb1a4964..124557489 100644 --- a/Session/Meta/AppDelegate.m +++ b/Session/Meta/AppDelegate.m @@ -697,11 +697,11 @@ static NSTimeInterval launchStartedAt; - (void)startOpenGroupPollersIfNeeded { - [SNOpenGroupManagerV2.shared startPolling]; + [SNOpenGroupManager.shared startPolling]; } - (void)stopOpenGroupPollers { - [SNOpenGroupManagerV2.shared stopPolling]; + [SNOpenGroupManager.shared stopPolling]; } # pragma mark - App Mode diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index fcd9b0a71..cf4597575 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -127,7 +127,7 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView fileprivate func joinOpenGroup(with string: String) { // A V2 open group URL will look like: + + + + // The host doesn't parse if no explicit scheme is provided - if let (room, server, publicKey) = OpenGroupManagerV2.parseV2OpenGroup(from: string) { + if let (room, server, publicKey) = OpenGroupManager.parseV2OpenGroup(from: string) { joinV2OpenGroup(room: room, server: server, publicKey: publicKey) } else { let title = NSLocalizedString("invalid_url", comment: "") @@ -141,7 +141,7 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView isJoining = true ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] _ in Storage.shared.write { transaction in - OpenGroupManagerV2.shared.add(room: room, server: server, publicKey: publicKey, using: transaction) + OpenGroupManager.shared.add(room: room, server: server, publicKey: publicKey, using: transaction) .done(on: DispatchQueue.main) { [weak self] _ in self?.presentingViewController?.dismiss(animated: true, completion: nil) let appDelegate = UIApplication.shared.delegate as! AppDelegate diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index 0d67c90fe..7fb5524c6 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -2,7 +2,7 @@ import PromiseKit import SessionSnodeKit @objc(LKBackgroundPoller) -public final class BackgroundPoller : NSObject { +public final class BackgroundPoller: NSObject { private static var closedGroupPoller: ClosedGroupPoller! private static var promises: [Promise] = [] @@ -11,20 +11,26 @@ public final class BackgroundPoller : NSObject { @objc(pollWithCompletionHandler:) public static func poll(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { promises = [] - promises.append(pollForMessages()) - promises.append(contentsOf: pollForClosedGroupMessages()) - let v2OpenGroupServers = Set(Storage.shared.getAllV2OpenGroups().values.map { $0.server }) - v2OpenGroupServers.forEach { server in - let poller = OpenGroupPollerV2(for: server) - poller.stop() - promises.append(poller.poll(isBackgroundPoll: true)) - } - when(resolved: promises).done { _ in - completionHandler(.newData) - }.catch { error in - SNLog("Background poll failed due to error: \(error)") - completionHandler(.failed) - } + .appending(pollForMessages()) + .appending(pollForClosedGroupMessages()) + .appending( + Set(Storage.shared.getAllV2OpenGroups().values.map { $0.server }) + .map { server in + let poller = OpenGroupAPI.Poller(for: server) + poller.stop() + + return poller.poll(isBackgroundPoll: true) + } + ) + + when(resolved: promises) + .done { _ in + completionHandler(.newData) + } + .catch { error in + SNLog("Background poll failed due to error: \(error)") + completionHandler(.failed) + } } private static func pollForMessages() -> Promise { @@ -38,22 +44,30 @@ public final class BackgroundPoller : NSObject { } private static func getMessages(for publicKey: String) -> Promise { - return SnodeAPI.getSwarm(for: publicKey).then(on: DispatchQueue.main) { swarm -> Promise in - guard let snode = swarm.randomElement() else { throw SnodeAPI.Error.generic } - return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) { - return SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey).then(on: DispatchQueue.main) { rawResponse -> Promise in - let messages = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) - let promises = messages.compactMap { json -> Promise? in - // Use a best attempt approach here; we don't want to fail the entire process if one of the - // messages failed to parse. - guard let envelope = SNProtoEnvelope.from(json), - let data = try? envelope.serializedData() else { return nil } - let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: true) - return job.execute() - } - return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects + return SnodeAPI.getSwarm(for: publicKey) + .then(on: DispatchQueue.main) { swarm -> Promise in + guard let snode = swarm.randomElement() else { throw SnodeAPI.Error.generic } + + return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) { + return SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey) + .then(on: DispatchQueue.main) { rawResponse -> Promise in + let messages = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) + let promises = messages + .compactMap { json -> Promise? in + // Use a best attempt approach here; we don't want to fail + // the entire process if one of the messages failed to parse. + guard let envelope = SNProtoEnvelope.from(json), let data = try? envelope.serializedData() else { + return nil + } + + let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: true) + + return job.execute() + } + + return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects + } } } - } } } diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index 0f909e71a..ecf87d375 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -87,7 +87,7 @@ public final class OpenGroupAPI: NSObject { } ) - // TODO: Handle response (maybe in the poller or the OpenGroupManagerV2?). + // TODO: Handle response (maybe in the poller or the OpenGroupManager?) return batch(server, requests: requestResponseType, using: dependencies) } @@ -127,7 +127,7 @@ public final class OpenGroupAPI: NSObject { let rooms: [String] = dependencies.storage.getAllV2OpenGroups().values .filter { $0.server == server } .map { $0.room } - let useMessageLimit = (hasPerformedInitialPoll[server] != true && timeSinceLastOpen > OpenGroupPollerV2.maxInactivityPeriod) + let useMessageLimit = (hasPerformedInitialPoll[server] != true && timeSinceLastOpen > OpenGroupAPI.Poller.maxInactivityPeriod) hasPerformedInitialPoll[server] = true @@ -666,7 +666,7 @@ public final class OpenGroupAPI: NSObject { // MARK: - General - // TODO: Shift this to the OpenGroupManagerV2? (seems more at place there than in the API). + // TODO: Shift this to the OpenGroupManager? (seems more at place there than in the API) public static func getDefaultRoomsIfNeeded(using dependencies: Dependencies = Dependencies()) { Storage.shared.write( with: { transaction in @@ -922,7 +922,7 @@ public final class OpenGroupAPI: NSObject { .filter { $0.server == server } .map { $0.room } var getAuthTokenPromises: [String: Promise] = [:] - let useMessageLimit = (hasPerformedInitialPoll[server] != true && timeSinceLastOpen > OpenGroupPollerV2.maxInactivityPeriod) + let useMessageLimit = (hasPerformedInitialPoll[server] != true && timeSinceLastOpen > OpenGroupAPI.Poller.maxInactivityPeriod) hasPerformedInitialPoll[server] = true diff --git a/SessionMessagingKit/Open Groups/OpenGroupManagerV2.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift similarity index 89% rename from SessionMessagingKit/Open Groups/OpenGroupManagerV2.swift rename to SessionMessagingKit/Open Groups/OpenGroupManager.swift index 3f6e42bbd..fee3e6b14 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManagerV2.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -1,26 +1,26 @@ import PromiseKit -@objc(SNOpenGroupManagerV2) -public final class OpenGroupManagerV2 : NSObject { - private var pollers: [String:OpenGroupPollerV2] = [:] // One for each server +@objc(SNOpenGroupManager) +public final class OpenGroupManager: NSObject { + @objc public static let shared = OpenGroupManager() + + private var pollers: [String: OpenGroupAPI.Poller] = [:] // One for each server private var isPolling = false - // MARK: Initialization - @objc public static let shared = OpenGroupManagerV2() - - private override init() { } - - // MARK: Polling + // MARK: - Polling @objc public func startPolling() { guard !isPolling else { return } + isPolling = true - let servers = Set(Storage.shared.getAllV2OpenGroups().values.map { $0.server }) - servers.forEach { server in - if let poller = pollers[server] { poller.stop() } // Should never occur - let poller = OpenGroupPollerV2(for: server) - poller.startIfNeeded() - pollers[server] = poller - } + pollers = Set(Storage.shared.getAllV2OpenGroups().values.map { $0.server }) + .reduce(into: [:]) { prev, server in + pollers[server]?.stop() // Should never occur + + let poller = OpenGroupAPI.Poller(for: server) + poller.startIfNeeded() + + prev[server] = poller + } } @objc public func stopPolling() { @@ -28,17 +28,22 @@ public final class OpenGroupManagerV2 : NSObject { pollers.removeAll() } - // MARK: Adding & Removing + // MARK: - Adding & Removing + public func add(room: String, server: String, publicKey: String, using transaction: Any) -> Promise { let storage = Storage.shared + // Clear any existing data if needed storage.removeLastMessageServerID(for: room, on: server, using: transaction) storage.removeLastDeletionServerID(for: room, on: server, using: transaction) storage.removeAuthToken(for: room, on: server, using: transaction) + // Store the public key storage.setOpenGroupPublicKey(for: server, to: publicKey, using: transaction) + let (promise, seal) = Promise.pending() let transaction = transaction as! YapDatabaseReadWriteTransaction + transaction.addCompletionQueue(DispatchQueue.global(qos: .userInitiated)) { // Get the group info // TODO: Remove this legacy method @@ -56,10 +61,10 @@ public final class OpenGroupManagerV2 : NSObject { // storage.setV2OpenGroup(openGroup, for: thread.uniqueId!, using: transaction) // }, completion: { // // Start the poller if needed -// if OpenGroupManagerV2.shared.pollers[server] == nil { +// if OpenGroupManager.shared.pollers[server] == nil { // let poller = OpenGroupPollerV2(for: server) // poller.startIfNeeded() -// OpenGroupManagerV2.shared.pollers[server] = poller +// OpenGroupManager.shared.pollers[server] = poller // } // // Fetch the group image // OpenGroupAPI.legacyGetGroupImage(for: room, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { data in @@ -110,15 +115,15 @@ public final class OpenGroupManagerV2 : NSObject { }, completion: { // Start the poller if needed - if OpenGroupManagerV2.shared.pollers[server] == nil { - let poller = OpenGroupPollerV2(for: server) + if OpenGroupManager.shared.pollers[server] == nil { + let poller = OpenGroupAPI.Poller(for: server) poller.startIfNeeded() - OpenGroupManagerV2.shared.pollers[server] = poller + OpenGroupManager.shared.pollers[server] = poller } // Fetch the group image (if there is one) - // TODO: Need to test this - // TODO: Clean this up (can we avoid the if/else with fancy promise wrangling?) + // TODO: Need to test this. + // TODO: Clean this up (can we avoid the if/else with fancy promise wrangling?). if let imageId: Int64 = room.imageId { OpenGroupAPI.roomImage(imageId, for: room.token, on: server) .done(on: DispatchQueue.global(qos: .userInitiated)) { data in diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 67e309d1f..eedaa0e84 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -233,8 +233,8 @@ extension MessageReceiver { } // Open groups for openGroupURL in message.openGroups { - if let (room, server, publicKey) = OpenGroupManagerV2.parseV2OpenGroup(from: openGroupURL) { - OpenGroupManagerV2.shared.add(room: room, server: server, publicKey: publicKey, using: transaction).retainUntilComplete() + if let (room, server, publicKey) = OpenGroupManager.parseV2OpenGroup(from: openGroupURL) { + OpenGroupManager.shared.add(room: room, server: server, publicKey: publicKey, using: transaction).retainUntilComplete() } } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift new file mode 100644 index 000000000..38097ce7c --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift @@ -0,0 +1,252 @@ +import PromiseKit +import SessionSnodeKit + +extension OpenGroupAPI { + public final class Poller { + private let server: String + private var timer: Timer? = nil + private var hasStarted = false + private var isPolling = false + + // MARK: - Settings + + internal static let maxInactivityPeriod: Double = (14 * 24 * 60 * 60) + private static let pollInterval: TimeInterval = 4 + + // MARK: - Lifecycle + + public init(for server: String) { + self.server = server + } + + @objc public func startIfNeeded() { + guard !hasStarted else { return } + + DispatchQueue.main.async { [weak self] in // Timers don't do well on background queues + self?.hasStarted = true + self?.timer = Timer.scheduledTimer(withTimeInterval: Poller.pollInterval, repeats: true) { _ in + self?.poll().retainUntilComplete() + } + self?.poll().retainUntilComplete() + } + } + + @objc public func stop() { + timer?.invalidate() + hasStarted = false + } + + // MARK: - Polling + + @discardableResult + public func poll() -> Promise { + return poll(isBackgroundPoll: false) + } + + @discardableResult + public func poll(isBackgroundPoll: Bool) -> Promise { + guard !self.isPolling else { return Promise.value(()) } + + self.isPolling = true + let (promise, seal) = Promise.pending() + promise.retainUntilComplete() + + OpenGroupAPI.poll(server) + .done(on: OpenGroupAPI.workQueue) { [weak self] response in + self?.isPolling = false + self?.handlePollResponse(response, isBackgroundPoll: isBackgroundPoll) + seal.fulfill(()) + } + .catch(on: OpenGroupAPI.workQueue) { [weak self] error in + SNLog("Open group polling failed due to error: \(error).") + self?.isPolling = false + seal.fulfill(()) // The promise is just used to keep track of when we're done + } + // OpenGroupAPI.compactPoll(server) + // OpenGroupAPI.legacyCompactPoll(server) + // .done(on: OpenGroupAPI.workQueue) { [weak self] response in + // guard let self = self else { return } + // self.isPolling = false + // response.results.forEach { self.handleCompactPollBody($0, isBackgroundPoll: isBackgroundPoll) } + // seal.fulfill(()) + // } + // .catch(on: OpenGroupAPI.workQueue) { error in + // SNLog("Open group polling failed due to error: \(error).") + // self.isPolling = false + // seal.fulfill(()) // The promise is just used to keep track of when we're done + // } + + return promise + } + + private func handlePollResponse(_ response: [OpenGroupAPI.Endpoint: (info: OnionRequestResponseInfoType, data: Codable)], isBackgroundPoll: Bool) { + let storage = SNMessagingKitConfiguration.shared.storage + + response.forEach { endpoint, response in + switch endpoint { + case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _): + guard let responseData: [OpenGroupAPI.Message] = response.data as? [OpenGroupAPI.Message] else { + //SNLog("Open group polling failed due to error: \(error).") + return // TODO: Throw error? + } + + handleMessages(responseData, roomToken: roomToken, isBackgroundPoll: isBackgroundPoll, using: storage) + + case .roomPollInfo(let roomToken, _): + guard let responseData: OpenGroupAPI.RoomPollInfo = response.data as? OpenGroupAPI.RoomPollInfo else { + //SNLog("Open group polling failed due to error: \(error).") + return // TODO: Throw error? + } + + handlePollInfo(responseData, roomToken: roomToken, isBackgroundPoll: isBackgroundPoll, using: storage) + + default: break // No custom handling needed + } + } + } + + // MARK: - Custom response handling + // TODO: Shift this logic to the OpenGroupManager? (seems like the place it should belong?) + + private func handleMessages(_ messages: [OpenGroupAPI.Message], roomToken: String, isBackgroundPoll: Bool, using storage: SessionMessagingKitStorageProtocol) { + // Sorting the messages by server ID before importing them fixes an issue where messages that quote older messages can't find those older messages + let openGroupID = "\(server).\(roomToken)" + let sortedMessages: [OpenGroupAPI.Message] = messages + .sorted { lhs, rhs in lhs.seqNo < rhs.seqNo } + + storage.write { transaction in + var messageServerIDsToRemove: [UInt64] = [] + + sortedMessages.forEach { message in + guard let base64EncodedString: String = message.base64EncodedData, let data = Data(base64Encoded: base64EncodedString), let sender: String = message.sender else { + // A message with no data has been deleted so add it to the list to remove + messageServerIDsToRemove.append(UInt64(message.seqNo)) + return + } + + let envelope = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: UInt64(floor(message.posted))) + envelope.setContent(data) + envelope.setSource(sender) + + do { + let data = try envelope.buildSerializedData() + let (message, proto) = try MessageReceiver.parse(data, openGroupMessageServerID: UInt64(message.seqNo), isRetry: false, using: transaction) + try MessageReceiver.handle(message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction) + } + catch { + SNLog("Couldn't receive open group message due to error: \(error).") + } + } + + // Handle any deletions that are needed + guard !messageServerIDsToRemove.isEmpty else { return } + guard let transaction: YapDatabaseReadWriteTransaction = transaction as? YapDatabaseReadWriteTransaction else { return } + guard let threadID = storage.v2GetThreadID(for: openGroupID), let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else { + return + } + + var messagesToRemove: [TSMessage] = [] + + thread.enumerateInteractions(with: transaction) { interaction, stop in + guard let message: TSMessage = interaction as? TSMessage, messageServerIDsToRemove.contains(message.openGroupServerMessageID) else { return } + messagesToRemove.append(message) + } + + messagesToRemove.forEach { $0.remove(with: transaction) } + } + } + + private func handlePollInfo(_ pollInfo: OpenGroupAPI.RoomPollInfo, roomToken: String, isBackgroundPoll: Bool, using storage: SessionMessagingKitStorageProtocol) { + // TODO: Handle other properties???. + + // public let token: String? + // public let created: TimeInterval? + // public let name: String? + // public let description: String? + // public let imageId: Int64? + // + // public let infoUpdates: Int64? + // public let messageSequence: Int64? + // public let activeUsers: Int64? + // public let activeUsersCutoff: Int64? + // public let pinnedMessages: [PinnedMessage]? + // + // public let admin: Bool? + // public let globalAdmin: Bool? + // public let admins: [String]? + // public let hiddenAdmins: [String]? + // + // public let moderator: Bool? + // public let globalModerator: Bool? + // public let moderators: [String]? + // public let hiddenModerators: [String]? + + // - Moderators + OpenGroupAPI.moderators[server] = (OpenGroupAPI.moderators[server] ?? [:]) + .setting(roomToken, Set(pollInfo.moderators ?? [])) + + // public let read: Bool? + // public let defaultRead: Bool? + // public let write: Bool? + // public let defaultWrite: Bool? + // public let upload: Bool? + // public let defaultUpload: Bool? + // + // /// Only populated and different if the `info_updates` counter differs from the provided `info_updated` value + // public let details: Room? + } + + // MARK: - Legacy Handling + + private func handleCompactPollBody(_ body: OpenGroupAPI.LegacyCompactPollResponse.Result, isBackgroundPoll: Bool) { + let storage = SNMessagingKitConfiguration.shared.storage + // - Messages + // Sorting the messages by server ID before importing them fixes an issue where messages that quote older messages can't find those older messages + let openGroupID = "\(server).\(body.room)" + let messages = (body.messages ?? []).sorted { ($0.serverID ?? 0) < ($1.serverID ?? 0) } + + storage.write { transaction in + messages.forEach { message in + guard let data = Data(base64Encoded: message.base64EncodedData) else { + return SNLog("Ignoring open group message with invalid encoding.") + } + let envelope = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: message.sentTimestamp) + envelope.setContent(data) + envelope.setSource(message.sender!) // Safe because messages with a nil sender are filtered out + do { + let data = try envelope.buildSerializedData() + let (message, proto) = try MessageReceiver.parse(data, openGroupMessageServerID: UInt64(message.serverID!), isRetry: false, using: transaction) + try MessageReceiver.handle(message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction) + } catch { + SNLog("Couldn't receive open group message due to error: \(error).") + } + } + } + + // - Moderators + if var x = OpenGroupAPI.moderators[server] { + x[body.room] = Set(body.moderators ?? []) + OpenGroupAPI.moderators[server] = x + } + else { + OpenGroupAPI.moderators[server] = [ body.room : Set(body.moderators ?? []) ] + } + + // - Deletions + let deletedMessageServerIDs = Set((body.deletions ?? []).map { UInt64($0.deletedMessageID) }) + storage.write { transaction in + let transaction = transaction as! YapDatabaseReadWriteTransaction + guard let threadID = storage.v2GetThreadID(for: openGroupID), + let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else { return } + var messagesToRemove: [TSMessage] = [] + + thread.enumerateInteractions(with: transaction) { interaction, stop in + guard let message = interaction as? TSMessage, deletedMessageServerIDs.contains(message.openGroupServerMessageID) else { return } + messagesToRemove.append(message) + } + + messagesToRemove.forEach { $0.remove(with: transaction) } + } + } + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift deleted file mode 100644 index 3497c884a..000000000 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift +++ /dev/null @@ -1,204 +0,0 @@ -import PromiseKit -import SessionSnodeKit - -@objc(SNOpenGroupPollerV2) -public final class OpenGroupPollerV2 : NSObject { - private let server: String - private var timer: Timer? = nil - private var hasStarted = false - private var isPolling = false - - // MARK: Settings - private let pollInterval: TimeInterval = 4 - static let maxInactivityPeriod: Double = 14 * 24 * 60 * 60 - - // MARK: Lifecycle - public init(for server: String) { - self.server = server - super.init() - } - - @objc public func startIfNeeded() { - guard !hasStarted else { return } - DispatchQueue.main.async { [weak self] in // Timers don't do well on background queues - guard let strongSelf = self else { return } - strongSelf.hasStarted = true - strongSelf.timer = Timer.scheduledTimer(withTimeInterval: strongSelf.pollInterval, repeats: true) { _ in - self?.poll().retainUntilComplete() - } - strongSelf.poll().retainUntilComplete() - } - } - - @objc public func stop() { - timer?.invalidate() - hasStarted = false - } - - // MARK: Polling - @discardableResult - public func poll() -> Promise { - return poll(isBackgroundPoll: false) - } - - @discardableResult - public func poll(isBackgroundPoll: Bool) -> Promise { - guard !self.isPolling else { return Promise.value(()) } - self.isPolling = true - let (promise, seal) = Promise.pending() - promise.retainUntilComplete() - - OpenGroupAPI.poll(server) - .done(on: OpenGroupAPI.workQueue) { [weak self] response in - self?.isPolling = false - self?.handlePollResponse(response, isBackgroundPoll: isBackgroundPoll) - seal.fulfill(()) - } - .catch(on: OpenGroupAPI.workQueue) { [weak self] error in - SNLog("Open group polling failed due to error: \(error).") - self?.isPolling = false - seal.fulfill(()) // The promise is just used to keep track of when we're done - } - - return promise - } - - private func handlePollResponse(_ response: [OpenGroupAPI.Endpoint: (info: OnionRequestResponseInfoType, data: Codable)], isBackgroundPoll: Bool) { - let storage = SNMessagingKitConfiguration.shared.storage - - response.forEach { endpoint, response in - switch endpoint { - case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _): - guard let responseData: [OpenGroupAPI.Message] = response.data as? [OpenGroupAPI.Message] else { - //SNLog("Open group polling failed due to error: \(error).") - return // TODO: Throw error? - } - - handleMessages(responseData, roomToken: roomToken, isBackgroundPoll: isBackgroundPoll, using: storage) - - case .roomPollInfo(let roomToken, _): - guard let responseData: OpenGroupAPI.RoomPollInfo = response.data as? OpenGroupAPI.RoomPollInfo else { - //SNLog("Open group polling failed due to error: \(error).") - return // TODO: Throw error? - } - - handlePollInfo(responseData, roomToken: roomToken, isBackgroundPoll: isBackgroundPoll, using: storage) - - default: break // No custom handling needed - } - } - } - - // MARK: - Custom response handling - // TODO: Shift this logic to the OpenGroupManagerV2? (seems like the place it should belong?) - - private func handleMessages(_ messages: [OpenGroupAPI.Message], roomToken: String, isBackgroundPoll: Bool, using storage: SessionMessagingKitStorageProtocol) { - // Sorting the messages by server ID before importing them fixes an issue where messages that quote older messages can't find those older messages - let openGroupID = "\(server).\(roomToken)" - let sortedMessages: [OpenGroupAPI.Message] = messages - .sorted { lhs, rhs in lhs.seqNo < rhs.seqNo } - - storage.write { transaction in - var messageServerIDsToRemove: [UInt64] = [] - - sortedMessages.forEach { message in - guard let base64EncodedString: String = message.base64EncodedData, let data = Data(base64Encoded: base64EncodedString), let sender: String = message.sender else { - // A message with no data has been deleted so add it to the list to remove - messageServerIDsToRemove.append(UInt64(message.seqNo)) - return - } - - let envelope = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: UInt64(floor(message.posted))) - envelope.setContent(data) - envelope.setSource(sender) - - do { - let data = try envelope.buildSerializedData() - let (message, proto) = try MessageReceiver.parse(data, openGroupMessageServerID: UInt64(message.seqNo), isRetry: false, using: transaction) - try MessageReceiver.handle(message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction) - } - catch { - SNLog("Couldn't receive open group message due to error: \(error).") - } - } - - // Handle any deletions that are needed - guard !messageServerIDsToRemove.isEmpty else { return } - guard let transaction: YapDatabaseReadWriteTransaction = transaction as? YapDatabaseReadWriteTransaction else { return } - guard let threadID = storage.v2GetThreadID(for: openGroupID), let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else { - return - } - - var messagesToRemove: [TSMessage] = [] - - thread.enumerateInteractions(with: transaction) { interaction, stop in - guard let message: TSMessage = interaction as? TSMessage, messageServerIDsToRemove.contains(message.openGroupServerMessageID) else { return } - messagesToRemove.append(message) - } - - messagesToRemove.forEach { $0.remove(with: transaction) } - } - } - - private func handlePollInfo(_ pollInfo: OpenGroupAPI.RoomPollInfo, roomToken: String, isBackgroundPoll: Bool, using storage: SessionMessagingKitStorageProtocol) { - // TODO: Handle other properties??? - - // - Moderators - OpenGroupAPI.moderators[server] = (OpenGroupAPI.moderators[server] ?? [:]) - .setting(roomToken, Set(pollInfo.moderators ?? [])) - - } - - // MARK: - Legacy Handling - - private func handleCompactPollBody(_ body: OpenGroupAPI.LegacyCompactPollResponse.Result, isBackgroundPoll: Bool) { - let storage = SNMessagingKitConfiguration.shared.storage - // - Messages - // Sorting the messages by server ID before importing them fixes an issue where messages that quote older messages can't find those older messages - let openGroupID = "\(server).\(body.room)" - let messages = (body.messages ?? []).sorted { ($0.serverID ?? 0) < ($1.serverID ?? 0) } - - storage.write { transaction in - messages.forEach { message in - guard let data = Data(base64Encoded: message.base64EncodedData) else { - return SNLog("Ignoring open group message with invalid encoding.") - } - let envelope = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: message.sentTimestamp) - envelope.setContent(data) - envelope.setSource(message.sender!) // Safe because messages with a nil sender are filtered out - do { - let data = try envelope.buildSerializedData() - let (message, proto) = try MessageReceiver.parse(data, openGroupMessageServerID: UInt64(message.serverID!), isRetry: false, using: transaction) - try MessageReceiver.handle(message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction) - } catch { - SNLog("Couldn't receive open group message due to error: \(error).") - } - } - } - - // - Moderators - if var x = OpenGroupAPI.moderators[server] { - x[body.room] = Set(body.moderators ?? []) - OpenGroupAPI.moderators[server] = x - } - else { - OpenGroupAPI.moderators[server] = [ body.room : Set(body.moderators ?? []) ] - } - - // - Deletions - let deletedMessageServerIDs = Set((body.deletions ?? []).map { UInt64($0.deletedMessageID) }) - storage.write { transaction in - let transaction = transaction as! YapDatabaseReadWriteTransaction - guard let threadID = storage.v2GetThreadID(for: openGroupID), - let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else { return } - var messagesToRemove: [TSMessage] = [] - - thread.enumerateInteractions(with: transaction) { interaction, stop in - guard let message = interaction as? TSMessage, deletedMessageServerIDs.contains(message.openGroupServerMessageID) else { return } - messagesToRemove.append(message) - } - - messagesToRemove.forEach { $0.remove(with: transaction) } - } - } -}