Renamed the OpenGroupPollerV2 and OpenGroupManagerV2

This commit is contained in:
Morgan Pretty 2022-02-15 16:44:10 +11:00
parent cd3dffcff9
commit 8cc9caa0fd
11 changed files with 356 additions and 288 deletions

View File

@ -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 = "<group>"; };
C3DA9C0625AE7396008F7C7E /* ConfigurationMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationMessage.swift; sourceTree = "<group>"; };
C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRCopyableLabel.swift; sourceTree = "<group>"; };
C3DB66AB260ACA42001EFC55 /* OpenGroupManagerV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupManagerV2.swift; sourceTree = "<group>"; };
C3DB66C2260ACCE6001EFC55 /* OpenGroupPollerV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupPollerV2.swift; sourceTree = "<group>"; };
C3DB66AB260ACA42001EFC55 /* OpenGroupManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupManager.swift; sourceTree = "<group>"; };
C3DB66C2260ACCE6001EFC55 /* OpenGroupPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupPoller.swift; sourceTree = "<group>"; };
C3DB66CB260AF1F3001EFC55 /* OpenGroupAPI+ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenGroupAPI+ObjC.swift"; sourceTree = "<group>"; };
C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sheet.swift; sourceTree = "<group>"; };
C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditClosedGroupVC.swift; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -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 */,

View File

@ -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)
}
}
}
}

View File

@ -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)

View File

@ -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

View File

@ -127,7 +127,7 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
fileprivate func joinOpenGroup(with string: String) {
// A V2 open group URL will look like: <optional scheme> + <host> + <optional port> + <room> + <public key>
// 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

View File

@ -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<Void>] = []
@ -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<Void> {
@ -38,22 +44,30 @@ public final class BackgroundPoller : NSObject {
}
private static func getMessages(for publicKey: String) -> Promise<Void> {
return SnodeAPI.getSwarm(for: publicKey).then(on: DispatchQueue.main) { swarm -> Promise<Void> 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<Void> in
let messages = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey)
let promises = messages.compactMap { json -> Promise<Void>? 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<Void> 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<Void> in
let messages = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey)
let promises = messages
.compactMap { json -> Promise<Void>? 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
}
}
}
}
}
}

View File

@ -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<String>] = [:]
let useMessageLimit = (hasPerformedInitialPoll[server] != true && timeSinceLastOpen > OpenGroupPollerV2.maxInactivityPeriod)
let useMessageLimit = (hasPerformedInitialPoll[server] != true && timeSinceLastOpen > OpenGroupAPI.Poller.maxInactivityPeriod)
hasPerformedInitialPoll[server] = true

View File

@ -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<Void> {
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<Void>.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

View File

@ -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()
}
}
}

View File

@ -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<Void> {
return poll(isBackgroundPoll: false)
}
@discardableResult
public func poll(isBackgroundPoll: Bool) -> Promise<Void> {
guard !self.isPolling else { return Promise.value(()) }
self.isPolling = true
let (promise, seal) = Promise<Void>.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) }
}
}
}
}

View File

@ -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<Void> {
return poll(isBackgroundPoll: false)
}
@discardableResult
public func poll(isBackgroundPoll: Bool) -> Promise<Void> {
guard !self.isPolling else { return Promise.value(()) }
self.isPolling = true
let (promise, seal) = Promise<Void>.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) }
}
}
}