Update other APIs for V2 open groups

This commit is contained in:
nielsandriesse 2021-03-24 12:37:33 +11:00
parent a5f831fd6c
commit 4c9728b4fe
9 changed files with 290 additions and 19 deletions

View File

@ -780,6 +780,9 @@
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 */; };
C3DB6695260AC923001EFC55 /* OpenGroupV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DB6694260AC923001EFC55 /* OpenGroupV2.swift */; };
C3DB66AC260ACA42001EFC55 /* OpenGroupManagerV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DB66AB260ACA42001EFC55 /* OpenGroupManagerV2.swift */; };
C3DB66C3260ACCE6001EFC55 /* OpenGroupPollerV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DB66C2260ACCE6001EFC55 /* OpenGroupPollerV2.swift */; };
C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */; };
C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */; };
C3ECBF7B257056B700EA7FCE /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ECBF7A257056B700EA7FCE /* Threading.swift */; };
@ -1777,6 +1780,9 @@
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>"; };
C3DB6694260AC923001EFC55 /* OpenGroupV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupV2.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>"; };
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>"; };
C3E7134E251C867C009649BB /* Sodium+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sodium+Conversion.swift"; sourceTree = "<group>"; };
@ -2502,7 +2508,9 @@
C3227FF4260AAD58006EA627 /* V2 */ = {
isa = PBXGroup;
children = (
C3DB6694260AC923001EFC55 /* OpenGroupV2.swift */,
B88FA7B726045D100049422F /* OpenGroupAPIV2.swift */,
C3DB66AB260ACA42001EFC55 /* OpenGroupManagerV2.swift */,
C3227FF5260AAD66006EA627 /* OpenGroupMessageV2.swift */,
);
path = V2;
@ -2511,8 +2519,10 @@
C3228005260AAD7E006EA627 /* V1 */ = {
isa = PBXGroup;
children = (
C3A721372558BDFA0043A11F /* OpenGroup.swift */,
C3A721352558BDF90043A11F /* OpenGroupAPI.swift */,
C3A721362558BDFA0043A11F /* OpenGroupInfo.swift */,
C3AAFFCB25AE92150089E6DD /* OpenGroupManager.swift */,
C3A721342558BDF90043A11F /* OpenGroupMessage.swift */,
C3A7229B2558E4310043A11F /* OpenGroupMessage+Conversion.swift */,
);
@ -2567,6 +2577,7 @@
children = (
C33FDB34255A580B00E217F9 /* ClosedGroupPoller.swift */,
C33FDA8C255A57FD00E217F9 /* OpenGroupPoller.swift */,
C3DB66C2260ACCE6001EFC55 /* OpenGroupPollerV2.swift */,
C33FDB3A255A580B00E217F9 /* Poller.swift */,
);
path = Pollers;
@ -3184,8 +3195,6 @@
children = (
C3228005260AAD7E006EA627 /* V1 */,
C3227FF4260AAD58006EA627 /* V2 */,
C3A721372558BDFA0043A11F /* OpenGroup.swift */,
C3AAFFCB25AE92150089E6DD /* OpenGroupManager.swift */,
);
path = "Open Groups";
sourceTree = "<group>";
@ -4772,6 +4781,7 @@
C3D9E52725677DF20040E4F3 /* OWSThumbnailService.swift in Sources */,
C32C5E75256DE020003C73A2 /* YapDatabaseTransaction+OWS.m in Sources */,
C3BBE0802554CDD70050F1E3 /* Storage.swift in Sources */,
C3DB66AC260ACA42001EFC55 /* OpenGroupManagerV2.swift in Sources */,
B8F5F61B25EDE4BF003BF8D4 /* DataExtractionNotificationInfoMessage.swift in Sources */,
C379DCF4256735770002D4EB /* VisibleMessage+Attachment.swift in Sources */,
B8856D34256F1192001CE70E /* Environment.m in Sources */,
@ -4836,6 +4846,7 @@
C3A3A0FE256E1A3C004D228D /* TSDatabaseSecondaryIndexes.m in Sources */,
C38D5E8D2575011E00B6A65C /* MessageSender+ClosedGroups.swift in Sources */,
C32C5B1C256DC19D003C73A2 /* TSQuotedMessage.m in Sources */,
C3DB6695260AC923001EFC55 /* OpenGroupV2.swift in Sources */,
C352A349255781F400338F3E /* AttachmentDownloadJob.swift in Sources */,
C352A30925574D8500338F3E /* Message+Destination.swift in Sources */,
C300A5E72554B07300555489 /* ExpirationTimerUpdate.swift in Sources */,
@ -4844,6 +4855,7 @@
C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */,
C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */,
B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */,
C3DB66C3260ACCE6001EFC55 /* OpenGroupPollerV2.swift in Sources */,
C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */,
C32C5FBB256E0206003C73A2 /* OWSBackgroundTask.m in Sources */,
B8856CA8256F0F42001CE70E /* OWSBackupFragment.m in Sources */,

View File

@ -3,33 +3,33 @@ extension Storage {
// MARK: - Open Groups
private static let openGroupCollection = "LokiPublicChatCollection"
private static let openGroupCollection = "SNOpenGroupCollection"
@objc public func getAllUserOpenGroups() -> [String:OpenGroup] {
var result = [String:OpenGroup]()
@objc public func getAllV2OpenGroups() -> [String:OpenGroupV2] {
var result = [String:OpenGroupV2]()
Storage.read { transaction in
transaction.enumerateKeysAndObjects(inCollection: Storage.openGroupCollection) { threadID, object, _ in
guard let openGroup = object as? OpenGroup else { return }
guard let openGroup = object as? OpenGroupV2 else { return }
result[threadID] = openGroup
}
}
return result
}
@objc(getOpenGroupForThreadID:)
public func getOpenGroup(for threadID: String) -> OpenGroup? {
var result: OpenGroup?
@objc(getV2OpenGroupForThreadID:)
public func getV2OpenGroup(for threadID: String) -> OpenGroupV2? {
var result: OpenGroupV2?
Storage.read { transaction in
result = transaction.object(forKey: threadID, inCollection: Storage.openGroupCollection) as? OpenGroup
result = transaction.object(forKey: threadID, inCollection: Storage.openGroupCollection) as? OpenGroupV2
}
return result
}
public func getThreadID(for openGroupID: String) -> String? {
public func v2GetThreadID(for v2OpenGroupID: String) -> String? {
var result: String?
Storage.read { transaction in
transaction.enumerateKeysAndObjects(inCollection: Storage.openGroupCollection, using: { threadID, object, stop in
guard let openGroup = object as? OpenGroup, "\(openGroup.server).\(openGroup.channel)" == openGroupID else { return }
guard let openGroup = object as? OpenGroupV2, openGroup.id == v2OpenGroupID else { return }
result = threadID
stop.pointee = true
})
@ -37,13 +37,13 @@ extension Storage {
return result
}
@objc(setOpenGroup:forThreadWithID:using:)
public func setOpenGroup(_ openGroup: OpenGroup, for threadID: String, using transaction: Any) {
@objc(setV2OpenGroup:forThreadWithID:using:)
public func setV2OpenGroup(_ openGroup: OpenGroupV2, for threadID: String, using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).setObject(openGroup, forKey: threadID, inCollection: Storage.openGroupCollection)
}
@objc(removeOpenGroupForThreadID:using:)
public func removeOpenGroup(for threadID: String, using transaction: Any) {
@objc(removeV2OpenGroupForThreadID:using:)
public func removeV2OpenGroup(for threadID: String, using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: threadID, inCollection: Storage.openGroupCollection)
}
@ -230,6 +230,50 @@ extension Storage {
// MARK: - Deprecated
private static let oldOpenGroupCollection = "LokiPublicChatCollection"
@objc public func getAllUserOpenGroups() -> [String:OpenGroup] {
var result = [String:OpenGroup]()
Storage.read { transaction in
transaction.enumerateKeysAndObjects(inCollection: Storage.oldOpenGroupCollection) { threadID, object, _ in
guard let openGroup = object as? OpenGroup else { return }
result[threadID] = openGroup
}
}
return result
}
@objc(getOpenGroupForThreadID:)
public func getOpenGroup(for threadID: String) -> OpenGroup? {
var result: OpenGroup?
Storage.read { transaction in
result = transaction.object(forKey: threadID, inCollection: Storage.oldOpenGroupCollection) as? OpenGroup
}
return result
}
public func getThreadID(for openGroupID: String) -> String? {
var result: String?
Storage.read { transaction in
transaction.enumerateKeysAndObjects(inCollection: Storage.oldOpenGroupCollection, using: { threadID, object, stop in
guard let openGroup = object as? OpenGroup, "\(openGroup.server).\(openGroup.channel)" == openGroupID else { return }
result = threadID
stop.pointee = true
})
}
return result
}
@objc(setOpenGroup:forThreadWithID:using:)
public func setOpenGroup(_ openGroup: OpenGroup, for threadID: String, using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).setObject(openGroup, forKey: threadID, inCollection: Storage.oldOpenGroupCollection)
}
@objc(removeOpenGroupForThreadID:using:)
public func removeOpenGroup(for threadID: String, using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: threadID, inCollection: Storage.oldOpenGroupCollection)
}
private static func getAuthTokenCollection(for server: String) -> String {
return (server == FileServerAPI.server) ? "LokiStorageAuthTokenCollection" : "LokiGroupChatAuthTokenCollection"
}

View File

@ -2,6 +2,7 @@ import PromiseKit
import SessionSnodeKit
// TODO: Message signature validation
// TODO: Keeping track of moderators
public enum OpenGroupAPIV2 {
@ -186,7 +187,7 @@ public enum OpenGroupAPIV2 {
}
return message
}
let serverID = messages.map { $0.serverID! }.max() ?? 0 // Safe because messages with a nil serverID are filtered out above
let serverID = messages.map { $0.serverID! }.max() ?? 0 // Safe because messages with a nil serverID are filtered out
let lastMessageServerID = storage.getLastMessageServerID(for: room, on: server) ?? 0
if serverID > lastMessageServerID {
let (promise, seal) = Promise<[OpenGroupMessageV2]>.pending()

View File

@ -0,0 +1,74 @@
import PromiseKit
@objc(SNOpenGroupManagerV2)
public final class OpenGroupManagerV2 : NSObject {
private var pollers: [String:OpenGroupPollerV2] = [:]
private var isPolling = false
// MARK: Initialization
@objc public static let shared = OpenGroupManagerV2()
private override init() { }
// MARK: Polling
@objc public func startPolling() {
guard !isPolling else { return }
isPolling = true
let openGroups = Storage.shared.getAllV2OpenGroups()
for (_, openGroup) in openGroups {
if let poller = pollers[openGroup.id] { poller.stop() } // Should never occur
let poller = OpenGroupPollerV2(for: openGroup)
poller.startIfNeeded()
pollers[openGroup.id] = poller
}
}
@objc public func stopPolling() {
pollers.forEach { (_, openGroupPoller) in openGroupPoller.stop() }
pollers.removeAll()
}
// MARK: Adding & Removing
public func add(room: String, server: String, name: String, using transaction: Any) -> Promise<Void> {
let storage = Storage.shared
storage.removeLastMessageServerID(for: room, on: server, using: transaction)
storage.removeLastDeletionServerID(for: room, on: server, using: transaction)
let openGroup = OpenGroupV2(server: server, room: room, name: name)
let groupID = LKGroupUtilities.getEncodedOpenGroupIDAsData(openGroup.id)
let model = TSGroupModel(title: openGroup.name, memberIds: [ getUserHexEncodedPublicKey() ], image: nil, groupId: groupID, groupType: .openGroup, adminIds: [])
storage.write(with: { transaction in
let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction as! YapDatabaseReadWriteTransaction)
storage.setV2OpenGroup(openGroup, for: thread.uniqueId!, using: transaction)
}, completion: {
if let poller = OpenGroupManagerV2.shared.pollers[openGroup.id] {
poller.stop()
OpenGroupManagerV2.shared.pollers[openGroup.id] = nil
}
let poller = OpenGroupPollerV2(for: openGroup)
poller.startIfNeeded()
OpenGroupManager.shared.pollers[openGroup.id] = poller
})
}
public func delete(_ openGroup: OpenGroupV2, associatedWith thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) {
if let poller = pollers[openGroup.id] {
poller.stop()
pollers[openGroup.id] = nil
}
var messageIDs: Set<String> = []
var messageTimestamps: Set<UInt64> = []
thread.enumerateInteractions(with: transaction) { interaction, _ in
messageIDs.insert(interaction.uniqueId!)
messageTimestamps.insert(interaction.timestamp)
}
SNMessagingKitConfiguration.shared.storage.updateMessageIDCollectionByPruningMessagesWithIDs(messageIDs, using: transaction)
Storage.shared.removeReceivedMessageTimestamps(messageTimestamps, using: transaction)
Storage.shared.removeLastMessageServerID(for: openGroup.room, on: openGroup.server, using: transaction)
Storage.shared.removeLastDeletionServerID(for: openGroup.room, on: openGroup.server, using: transaction)
let _ = OpenGroupAPIV2.deleteAuthToken(for: openGroup.room, on: openGroup.server)
Storage.shared.removeOpenGroupPublicKey(for: openGroup.server, using: transaction)
thread.removeAllThreadInteractions(with: transaction)
thread.remove(with: transaction)
Storage.shared.removeV2OpenGroup(for: thread.uniqueId!, using: transaction)
}
}

View File

@ -0,0 +1,32 @@
@objc(SNOpenGroupV2)
public final class OpenGroupV2 : NSObject, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility
public let server: String
public let room: String
public let id: String
public let name: String
public init(server: String, room: String, name: String) {
self.server = server.lowercased()
self.room = room
self.id = "\(server).\(room)"
self.name = name
}
// MARK: Coding
public init?(coder: NSCoder) {
server = coder.decodeObject(forKey: "server") as! String
room = coder.decodeObject(forKey: "room") as! String
self.id = "\(server).\(room)"
name = coder.decodeObject(forKey: "name") as! String
super.init()
}
public func encode(with coder: NSCoder) {
coder.encode(server, forKey: "server")
coder.encode(room, forKey: "room")
coder.encode(name, forKey: "name")
}
override public var description: String { "\(name) (\(server)\(room)" }
}

View File

@ -0,0 +1,105 @@
import PromiseKit
@objc(SNOpenGroupPollerV2)
public final class OpenGroupPollerV2 : NSObject {
private let openGroup: OpenGroupV2
private var pollForNewMessagesTimer: Timer? = nil
private var pollForDeletedMessagesTimer: Timer? = nil
private var pollForModeratorsTimer: Timer? = nil
private var hasStarted = false
private var isPolling = false
private var isMainAppAndActive: Bool {
var isMainAppAndActive = false
if let sharedUserDefaults = UserDefaults(suiteName: "group.com.loki-project.loki-messenger") {
isMainAppAndActive = sharedUserDefaults.bool(forKey: "isMainAppActive")
}
return isMainAppAndActive
}
// MARK: Settings
private let pollForNewMessagesInterval: TimeInterval = 4
private let pollForDeletedMessagesInterval: TimeInterval = 30
private let pollForModeratorsInterval: TimeInterval = 10 * 60
// MARK: Lifecycle
public init(for openGroup: OpenGroupV2) {
self.openGroup = openGroup
super.init()
}
@objc public func startIfNeeded() {
guard !hasStarted else { return }
guard isMainAppAndActive else { stop(); return }
DispatchQueue.main.async { [weak self] in // Timers don't do well on background queues
guard let strongSelf = self else { return }
strongSelf.hasStarted = true
// Create timers
strongSelf.pollForNewMessagesTimer = Timer.scheduledTimer(withTimeInterval: strongSelf.pollForNewMessagesInterval, repeats: true) { _ in self?.pollForNewMessages() }
strongSelf.pollForDeletedMessagesTimer = Timer.scheduledTimer(withTimeInterval: strongSelf.pollForDeletedMessagesInterval, repeats: true) { _ in self?.pollForDeletedMessages() }
strongSelf.pollForModeratorsTimer = Timer.scheduledTimer(withTimeInterval: strongSelf.pollForModeratorsInterval, repeats: true) { _ in self?.pollForModerators() }
// Perform initial updates
strongSelf.pollForNewMessages()
strongSelf.pollForDeletedMessages()
strongSelf.pollForModerators()
}
}
@objc public func stop() {
pollForNewMessagesTimer?.invalidate()
pollForDeletedMessagesTimer?.invalidate()
pollForModeratorsTimer?.invalidate()
hasStarted = false
}
// MARK: Polling
@discardableResult
public func pollForNewMessages() -> Promise<Void> {
guard isMainAppAndActive else { stop(); return Promise.value(()) }
return pollForNewMessages(isBackgroundPoll: false)
}
@discardableResult
public func pollForNewMessages(isBackgroundPoll: Bool) -> Promise<Void> {
guard !self.isPolling else { return Promise.value(()) }
self.isPolling = true
let openGroup = self.openGroup
let (promise, seal) = Promise<Void>.pending()
promise.retainUntilComplete()
OpenGroupAPIV2.getMessages(for: openGroup.room, on: openGroup.server).done(on: DispatchQueue.global(qos: .default)) { [weak self] messages in
guard let self = self else { return }
self.isPolling = false
// 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 messages = messages.sorted { $0.serverID! < $1.serverID! } // Safe because messages with a nil serverID are filtered out
messages.forEach { message in
guard let data = Data(base64Encoded: message.base64EncodedData) else {
return SNLog("Ignoring open group message with invalid encoding.")
}
let job = MessageReceiveJob(data: data, openGroupMessageServerID: UInt64(message.serverID!), openGroupID: self.openGroup.id, isBackgroundPoll: isBackgroundPoll)
SNMessagingKitConfiguration.shared.storage.write { transaction in
SessionMessagingKit.JobQueue.shared.add(job, using: transaction)
}
}
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { _ in
seal.fulfill(()) // The promise is just used to keep track of when we're done
}.retainUntilComplete()
return promise
}
private func pollForDeletedMessages() {
let openGroup = self.openGroup
OpenGroupAPIV2.getDeletedMessages(for: openGroup.room, on: openGroup.server).done(on: DispatchQueue.global(qos: .default)) { serverIDs in
let messageIDs = serverIDs.compactMap { Storage.shared.getIDForMessage(withServerID: UInt64($0)) }
SNMessagingKitConfiguration.shared.storage.write { transaction in
let transaction = transaction as! YapDatabaseReadWriteTransaction
messageIDs.forEach { messageID in
TSMessage.fetch(uniqueId: messageID, transaction: transaction)?.remove(with: transaction)
}
}
}.retainUntilComplete()
}
private func pollForModerators() {
OpenGroupAPIV2.getModerators(for: openGroup.room, on: openGroup.server).retainUntilComplete()
}
}

View File

@ -43,8 +43,8 @@ public protocol SessionMessagingKitStorageProtocol {
// MARK: - Open Groups
func getAllUserOpenGroups() -> [String:OpenGroup]
func getOpenGroup(for threadID: String) -> OpenGroup?
func getAllV2OpenGroups() -> [String:OpenGroupV2]
func getV2OpenGroup(for threadID: String) -> OpenGroupV2?
func getThreadID(for openGroupID: String) -> String?
func updateMessageIDCollectionByPruningMessagesWithIDs(_ messageIDs: Set<String>, using transaction: Any)
@ -101,4 +101,7 @@ public protocol SessionMessagingKitStorageProtocol {
func getLastDeletionServerID(for group: UInt64, on server: String) -> UInt64?
func setLastDeletionServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any)
func removeLastDeletionServerID(for group: UInt64, on server: String, using transaction: Any)
func getAllUserOpenGroups() -> [String:OpenGroup]
func getOpenGroup(for threadID: String) -> OpenGroup?
}