From a8b53f6cf3da69a3e06a51a5f8f9d6cb7b73af22 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 15 Sep 2020 11:30:45 +1000 Subject: [PATCH] Update background polling for SSKs --- Signal.xcodeproj/project.pbxproj | 4 +++ Signal/src/AppDelegate.m | 30 +------------------ .../src/Loki/Utilities/BackgroundPoller.swift | 28 +++++++++++++++++ SignalServiceKit/src/Loki/API/SnodeAPI.swift | 2 +- .../Closed Groups/ClosedGroupPoller.swift | 21 +++++++++---- .../Utilities/AnyPromise+Conversion.swift | 4 +-- 6 files changed, 51 insertions(+), 38 deletions(-) create mode 100644 Signal/src/Loki/Utilities/BackgroundPoller.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 01a084fe9..15ecae906 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -586,6 +586,7 @@ C39DD28A24F3336E008590FC /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C39DD28724F3318C008590FC /* Colors.xcassets */; }; C39DD28B24F3336F008590FC /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C39DD28724F3318C008590FC /* Colors.xcassets */; }; C3C3CF8924D8EED300E1CCE7 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C3CF8824D8EED300E1CCE7 /* TextView.swift */; }; + C3D0972B2510499C00F6E3E4 /* BackgroundPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D0972A2510499C00F6E3E4 /* BackgroundPoller.swift */; }; C3DAB3242480CB2B00725F25 /* SRCopyableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */; }; C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */; }; C3DFFAC823E970080058DAF8 /* OpenGroupSuggestionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC723E970080058DAF8 /* OpenGroupSuggestionSheet.swift */; }; @@ -1376,6 +1377,7 @@ C3AA6BB824CE8F1B002358B6 /* Migrating Translations from Android.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Migrating Translations from Android.md"; sourceTree = ""; }; C3AECBEA24EF5244005743DE /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = translations/fa.lproj/Localizable.strings; sourceTree = ""; }; C3C3CF8824D8EED300E1CCE7 /* TextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = ""; }; + C3D0972A2510499C00F6E3E4 /* BackgroundPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundPoller.swift; sourceTree = ""; }; C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRCopyableLabel.swift; sourceTree = ""; }; C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sheet.swift; sourceTree = ""; }; C3DFFAC723E970080058DAF8 /* OpenGroupSuggestionSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupSuggestionSheet.swift; sourceTree = ""; }; @@ -2672,6 +2674,7 @@ isa = PBXGroup; children = ( B8544E3223D50E4900299F14 /* AppearanceUtilities.swift */, + C3D0972A2510499C00F6E3E4 /* BackgroundPoller.swift */, C31A6C5B247F2CF3001123EF /* CGRect+Utilities.swift */, C35E8AAD2485E51D00ACB629 /* IP2Country.swift */, B84664F4235022F30083A1CD /* MentionUtilities.swift */, @@ -3802,6 +3805,7 @@ 3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */, 45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */, 340FC8A9204DAC8D007AEB0F /* NotificationSettingsOptionsViewController.m in Sources */, + C3D0972B2510499C00F6E3E4 /* BackgroundPoller.swift in Sources */, C3548F0624456447009433A8 /* PNModeVC.swift in Sources */, B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */, 452037D11EE84975004E4CDF /* DebugUISessionState.m in Sources */, diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index c5bd1c2f0..a24f729df 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -357,36 +357,8 @@ static NSTimeInterval launchStartedAt; - (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { - NSLog(@"[Loki] Performing background fetch."); [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - NSMutableArray *promises = [NSMutableArray new]; - - __block AnyPromise *fetchMessagesPromise = [AppEnvironment.shared.messageFetcherJob run].then(^{ - fetchMessagesPromise = nil; - }).catch(^{ - fetchMessagesPromise = nil; - }); - [promises addObject:fetchMessagesPromise]; - [fetchMessagesPromise retainUntilComplete]; - - __block NSDictionary *publicChats; - [OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - publicChats = [LKDatabaseUtilities getAllPublicChats:transaction]; - }]; - for (LKPublicChat *publicChat in publicChats) { - if (![publicChat isKindOfClass:LKPublicChat.class]) { continue; } - LKPublicChatPoller *poller = [[LKPublicChatPoller alloc] initForPublicChat:publicChat]; - [poller stop]; - AnyPromise *fetchGroupMessagesPromise = [poller pollForNewMessages]; - [promises addObject:fetchGroupMessagesPromise]; - [fetchGroupMessagesPromise retainUntilComplete]; - } - - PMKJoin(promises).then(^(id results) { - completionHandler(UIBackgroundFetchResultNewData); - }).catch(^(id error) { - completionHandler(UIBackgroundFetchResultFailed); - }); + [LKBackgroundPoller pollWithCompletionHandler:completionHandler]; }]; } diff --git a/Signal/src/Loki/Utilities/BackgroundPoller.swift b/Signal/src/Loki/Utilities/BackgroundPoller.swift new file mode 100644 index 000000000..cfa67d46d --- /dev/null +++ b/Signal/src/Loki/Utilities/BackgroundPoller.swift @@ -0,0 +1,28 @@ +import PromiseKit + +@objc(LKBackgroundPoller) +public final class BackgroundPoller : NSObject { + + private override init() { } + + @objc(pollWithCompletionHandler:) + public static func poll(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + var promises: [Promise] = [] + promises.append(AppEnvironment.shared.messageFetcherJob.run()) // FIXME: It'd be nicer to just use Poller directly + promises.append(contentsOf: ClosedGroupPoller().pollOnce()) + var openGroups: [String:PublicChat] = [:] + Storage.read { transaction in + openGroups = LokiDatabaseUtilities.getAllPublicChats(in: transaction) + } + openGroups.values.forEach { openGroup in + let poller = PublicChatPoller(for: openGroup) + poller.stop() + promises.append(poller.pollForNewMessages()) + } + when(resolved: promises).done { _ in + completionHandler(.newData) + }.catch { _ in + completionHandler(.failed) + } + } +} diff --git a/SignalServiceKit/src/Loki/API/SnodeAPI.swift b/SignalServiceKit/src/Loki/API/SnodeAPI.swift index 161fed6ee..f7ed65bc4 100644 --- a/SignalServiceKit/src/Loki/API/SnodeAPI.swift +++ b/SignalServiceKit/src/Loki/API/SnodeAPI.swift @@ -245,7 +245,7 @@ public final class SnodeAPI : NSObject { guard let json = rawResponse as? JSON, let rawMessages = json["messages"] as? [JSON] else { return [] } if let (lastHash, expirationDate) = updateLastMessageHashValueIfPossible(for: snode, associatedWith: publicKey, from: rawMessages), UserDefaults.standard[.isUsingFullAPNs] { - LokiPushNotificationManager.acknowledgeDelivery(forMessageWithHash: lastHash, expiration: expirationDate, hexEncodedPublicKey: getUserHexEncodedPublicKey()) + LokiPushNotificationManager.acknowledgeDelivery(forMessageWithHash: lastHash, expiration: expirationDate, publicKey: getUserHexEncodedPublicKey()) } let rawNewMessages = removeDuplicates(from: rawMessages, associatedWith: publicKey) let newMessages = parseProtoEnvelopes(from: rawNewMessages) diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupPoller.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupPoller.swift index f07c17b6b..3db7d48eb 100644 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupPoller.swift +++ b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupPoller.swift @@ -31,24 +31,31 @@ public final class ClosedGroupPoller : NSObject { } } + public func pollOnce() -> [Promise] { + guard !isPolling else { return [] } + isPolling = true + return poll() + } + @objc public func stop() { isPolling = false timer?.invalidate() } // MARK: Private API - private func poll() { - guard isPolling else { return } + private func poll() -> [Promise] { + guard isPolling else { return [] } let publicKeys = Storage.getUserClosedGroupPublicKeys() - publicKeys.forEach { publicKey in - SnodeAPI.getSwarm(for: publicKey).then2 { [weak self] swarm -> Promise<[SSKProtoEnvelope]> in + return publicKeys.map { publicKey in + let promise = SnodeAPI.getSwarm(for: publicKey).then2 { [weak self] swarm -> Promise<[SSKProtoEnvelope]> in // randomElement() uses the system's default random generator, which is cryptographically secure guard let snode = swarm.randomElement() else { return Promise(error: Error.insufficientSnodes) } guard let self = self, self.isPolling else { return Promise(error: Error.pollingCanceled) } return SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey).map2 { SnodeAPI.parseRawMessagesResponse($0, from: snode, associatedWith: publicKey) } - }.done2 { [weak self] messages in + } + promise.done2 { [weak self] messages in guard let self = self, self.isPolling else { return } if !messages.isEmpty { print("[Loki] Received \(messages.count) new message(s) in closed group with public key: \(publicKey).") @@ -61,9 +68,11 @@ public final class ClosedGroupPoller : NSObject { print("[Loki] Failed to deserialize envelope due to error: \(error).") } } - }.catch2 { error in + } + promise.catch2 { error in print("[Loki] Polling failed for closed group with public key: \(publicKey) due to error: \(error).") } + return promise.map { _ in } } } } diff --git a/SignalServiceKit/src/Loki/Utilities/AnyPromise+Conversion.swift b/SignalServiceKit/src/Loki/Utilities/AnyPromise+Conversion.swift index 25480fd6b..1c15fc554 100644 --- a/SignalServiceKit/src/Loki/Utilities/AnyPromise+Conversion.swift +++ b/SignalServiceKit/src/Loki/Utilities/AnyPromise+Conversion.swift @@ -1,8 +1,8 @@ import PromiseKit -internal extension AnyPromise { +public extension AnyPromise { - internal static func from(_ promise: Promise) -> AnyPromise { + public static func from(_ promise: Promise) -> AnyPromise { let result = AnyPromise(promise) result.retainUntilComplete() return result