Renamed the OpenGroupPollerV2 and OpenGroupManagerV2
This commit is contained in:
parent
cd3dffcff9
commit
8cc9caa0fd
|
@ -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 */,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue