diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index f4998ffdb..906428ff1 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -774,6 +774,8 @@ F5765D284BC6ECAC0C1D33F0 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4A93ECA93B3DE800CC7D7F6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */; }; FC3BD9881A30A790005B96BB /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC3BD9871A30A790005B96BB /* Social.framework */; }; FCB11D8C1A129A76002F93FB /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCB11D8B1A129A76002F93FB /* CoreMedia.framework */; }; + FD3C907327E8387300CD579F /* OpenGroupServerIdLookupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C907227E8387300CD579F /* OpenGroupServerIdLookupMigration.swift */; }; + FD3C907527E83AC200CD579F /* OpenGroupServerIdLookup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */; }; FD5D200F27AA2B6000FEA984 /* MessageRequestResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D200E27AA2B6000FEA984 /* MessageRequestResponse.swift */; }; FD5D201127AA331F00FEA984 /* ConfigurationMessage+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D201027AA331F00FEA984 /* ConfigurationMessage+Convenience.swift */; }; FD659AC027A7649600F12C02 /* MessageRequestsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD659ABF27A7649600F12C02 /* MessageRequestsViewController.swift */; }; @@ -787,6 +789,7 @@ FD88BAD927A7439C00BBC442 /* MessageRequestsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */; }; FD88BADB27A750F200BBC442 /* MessageRequestsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */; }; FDC4389E27BA2B8A00C60D73 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; + FDFD645827EC1F4000808CA1 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645727EC1F4000808CA1 /* Atomic.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1816,6 +1819,8 @@ F9BBF530D71905BA9007675F /* Pods-SessionShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionShareExtension/Pods-SessionShareExtension.debug.xcconfig"; sourceTree = ""; }; FC3BD9871A30A790005B96BB /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; }; FCB11D8B1A129A76002F93FB /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; + FD3C907227E8387300CD579F /* OpenGroupServerIdLookupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookupMigration.swift; sourceTree = ""; }; + FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookup.swift; sourceTree = ""; }; FD5D200E27AA2B6000FEA984 /* MessageRequestResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestResponse.swift; sourceTree = ""; }; FD5D201027AA331F00FEA984 /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = ""; }; FD659ABF27A7649600F12C02 /* MessageRequestsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewController.swift; sourceTree = ""; }; @@ -1829,6 +1834,7 @@ FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsCell.swift; sourceTree = ""; }; FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsMigration.swift; sourceTree = ""; }; FD9039443F7CB729CF71350E /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; sourceTree = ""; }; + FDFD645727EC1F4000808CA1 /* Atomic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = ""; }; FEDBAE1B98C49BBE8C87F575 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; sourceTree = ""; }; FF9BA33D021B115B1F5B4E46 /* Pods-SessionMessagingKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionMessagingKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionMessagingKit/Pods-SessionMessagingKit.app store release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -2339,6 +2345,7 @@ C33FDB8A255A581200E217F9 /* AppContext.h */, C33FDB85255A581100E217F9 /* AppContext.m */, C3C2A5D12553860800C340D1 /* Array+Utilities.swift */, + FDFD645727EC1F4000808CA1 /* Atomic.swift */, C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */, B8F5F58225EC94A6003BF8D4 /* Collection+Subscripting.swift */, B8AE75A325A6C6A6001A84D2 /* Data+Trimming.swift */, @@ -3062,6 +3069,7 @@ children = ( B8B32044258C117C0020074B /* ContactsMigration.swift */, FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */, + FD3C907227E8387300CD579F /* OpenGroupServerIdLookupMigration.swift */, C38EF271255B6D79007E1867 /* OWSDatabaseMigration.h */, C38EF270255B6D79007E1867 /* OWSDatabaseMigration.m */, C38EF26F255B6D79007E1867 /* OWSDatabaseMigrationRunner.h */, @@ -3191,6 +3199,7 @@ isa = PBXGroup; children = ( C3DB6694260AC923001EFC55 /* OpenGroupV2.swift */, + FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */, B88FA7B726045D100049422F /* OpenGroupAPIV2.swift */, C3DB66CB260AF1F3001EFC55 /* OpenGroupAPIV2+ObjC.swift */, C3DB66AB260ACA42001EFC55 /* OpenGroupManagerV2.swift */, @@ -4530,6 +4539,7 @@ C38EF407255B6DF7007E1867 /* Toast.swift in Sources */, C38EF38C255B6DD2007E1867 /* ApprovalRailCellView.swift in Sources */, C38EF409255B6DF7007E1867 /* ContactTableViewCell.m in Sources */, + FD3C907327E8387300CD579F /* OpenGroupServerIdLookupMigration.swift in Sources */, C38EF32A255B6DBF007E1867 /* UIUtil.m in Sources */, C38EF335255B6DBF007E1867 /* BlockListCache.swift in Sources */, C38EF2A6255B6D93007E1867 /* PlaceholderIcon.swift in Sources */, @@ -4680,6 +4690,7 @@ C32C5DDB256DD9FF003C73A2 /* ContentProxy.swift in Sources */, C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */, B8F5F58325EC94A6003BF8D4 /* Collection+Subscripting.swift in Sources */, + FDFD645827EC1F4000808CA1 /* Atomic.swift in Sources */, C33FDEF8255A656D00E217F9 /* Promise+Delaying.swift in Sources */, B8BC00C0257D90E30032E807 /* General.swift in Sources */, C32C5A24256DB7DB003C73A2 /* SNUserDefaults.swift in Sources */, @@ -4766,6 +4777,7 @@ B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */, C3C2A7712553A41E00C340D1 /* ControlMessage.swift in Sources */, C32C5D19256DD493003C73A2 /* OWSLinkPreview.swift in Sources */, + FD3C907527E83AC200CD579F /* OpenGroupServerIdLookup.swift in Sources */, C32C5CF0256DD3E4003C73A2 /* Storage+Shared.swift in Sources */, C300A5BD2554B00D00555489 /* ReadReceipt.swift in Sources */, C32C5AB5256DBE8F003C73A2 /* TSOutgoingMessage+Conversion.swift in Sources */, @@ -5173,7 +5185,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 329; + CURRENT_PROJECT_VERSION = 333; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5198,7 +5210,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.11.23; + MARKETING_VERSION = 1.11.24; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5246,7 +5258,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 329; + CURRENT_PROJECT_VERSION = 333; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5276,7 +5288,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.11.23; + MARKETING_VERSION = 1.11.24; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5312,7 +5324,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 329; + CURRENT_PROJECT_VERSION = 333; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5335,7 +5347,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.11.23; + MARKETING_VERSION = 1.11.24; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -5386,7 +5398,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 329; + CURRENT_PROJECT_VERSION = 333; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5414,7 +5426,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.11.23; + MARKETING_VERSION = 1.11.24; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -6322,7 +6334,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 329; + CURRENT_PROJECT_VERSION = 333; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6361,7 +6373,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.11.23; + MARKETING_VERSION = 1.11.24; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; @@ -6393,7 +6405,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 329; + CURRENT_PROJECT_VERSION = 333; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6432,7 +6444,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.11.23; + MARKETING_VERSION = 1.11.24; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_NAME = Session; diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 7e3aec685..0b26218e7 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -263,49 +263,46 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc let linkPreviewDraft = snInputView.linkPreviewInfo?.draft let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread) - Storage.write(with: { transaction in - let promise: Promise = self.approveMessageRequestIfNeeded( - for: self.thread, - with: transaction, - isNewThread: !oldThreadShouldBeVisible, - timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting - ) - .map { [weak self] _ in - self?.viewModel.appendUnsavedOutgoingTextMessage(tsMessage) - - Storage.write(with: { transaction in - message.linkPreview = VisibleMessage.LinkPreview.from(linkPreviewDraft, using: transaction) - }, completion: { [weak self] in - tsMessage.linkPreview = OWSLinkPreview.from(message.linkPreview) - - Storage.shared.write( - with: { transaction in - tsMessage.save(with: transaction as! YapDatabaseReadWriteTransaction) - }, - completion: { [weak self] in - // At this point the TSOutgoingMessage should have its link preview set, so we can scroll to the bottom knowing - // the height of the new message cell - self?.scrollToBottom(isAnimated: false) - } - ) - - Storage.shared.write { transaction in - MessageSender.send(message, with: [], in: thread, using: transaction as! YapDatabaseReadWriteTransaction) + let promise: Promise = self.approveMessageRequestIfNeeded( + for: self.thread, + isNewThread: !oldThreadShouldBeVisible, + timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting + ) + .map { [weak self] _ in + self?.viewModel.appendUnsavedOutgoingTextMessage(tsMessage) + + Storage.write(with: { transaction in + message.linkPreview = VisibleMessage.LinkPreview.from(linkPreviewDraft, using: transaction) + }, completion: { [weak self] in + tsMessage.linkPreview = OWSLinkPreview.from(message.linkPreview) + + Storage.shared.write( + with: { transaction in + tsMessage.save(with: transaction as! YapDatabaseReadWriteTransaction) + }, + completion: { [weak self] in + // At this point the TSOutgoingMessage should have its link preview set, so we can scroll to the bottom knowing + // the height of the new message cell + self?.scrollToBottom(isAnimated: false) } - - self?.handleMessageSent() - }) - } - - // Show an error indicating that approving the thread failed - promise.catch(on: DispatchQueue.main) { [weak self] _ in - let alert = UIAlertController(title: "Session", message: "An error occurred when trying to accept this message request", preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - self?.present(alert, animated: true, completion: nil) - } - - promise.retainUntilComplete() - }) + ) + + Storage.shared.write { transaction in + MessageSender.send(message, with: [], in: thread, using: transaction as! YapDatabaseReadWriteTransaction) + } + + self?.handleMessageSent() + }) + } + + // Show an error indicating that approving the thread failed + promise.catch(on: DispatchQueue.main) { [weak self] _ in + let alert = UIAlertController(title: "Session", message: "An error occurred when trying to accept this message request", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self?.present(alert, animated: true, completion: nil) + } + + promise.retainUntilComplete() } func sendAttachments(_ attachments: [SignalAttachment], with text: String, onComplete: (() -> ())? = nil) { @@ -329,44 +326,41 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc let oldThreadShouldBeVisible: Bool = thread.shouldBeVisible let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread) - Storage.write(with: { transaction in - let promise: Promise = self.approveMessageRequestIfNeeded( - for: self.thread, - with: transaction, - isNewThread: !oldThreadShouldBeVisible, - timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting - ) - .map { [weak self] _ in - Storage.write( - with: { transaction in - tsMessage.save(with: transaction) - // The new message cell is inserted at this point, but the TSOutgoingMessage doesn't have its attachment yet - }, - completion: { [weak self] in - Storage.write(with: { transaction in - MessageSender.send(message, with: attachments, in: thread, using: transaction) - }, completion: { [weak self] in - // At this point the TSOutgoingMessage should have its attachments set, so we can scroll to the bottom knowing - // the height of the new message cell - self?.scrollToBottom(isAnimated: false) - }) - self?.handleMessageSent() - - // Attachment successfully sent - dismiss the screen - onComplete?() - } - ) - } - - // Show an error indicating that approving the thread failed - promise.catch(on: DispatchQueue.main) { [weak self] _ in - let alert = UIAlertController(title: "Session", message: "An error occurred when trying to accept this message request", preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - self?.present(alert, animated: true, completion: nil) - } + let promise: Promise = self.approveMessageRequestIfNeeded( + for: self.thread, + isNewThread: !oldThreadShouldBeVisible, + timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting + ) + .map { [weak self] _ in + Storage.write( + with: { transaction in + tsMessage.save(with: transaction) + // The new message cell is inserted at this point, but the TSOutgoingMessage doesn't have its attachment yet + }, + completion: { [weak self] in + Storage.write(with: { transaction in + MessageSender.send(message, with: attachments, in: thread, using: transaction) + }, completion: { [weak self] in + // At this point the TSOutgoingMessage should have its attachments set, so we can scroll to the bottom knowing + // the height of the new message cell + self?.scrollToBottom(isAnimated: false) + }) + self?.handleMessageSent() - promise.retainUntilComplete() - }) + // Attachment successfully sent - dismiss the screen + onComplete?() + } + ) + } + + // Show an error indicating that approving the thread failed + promise.catch(on: DispatchQueue.main) { [weak self] _ in + let alert = UIAlertController(title: "Session", message: "An error occurred when trying to accept this message request", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self?.present(alert, animated: true, completion: nil) + } + + promise.retainUntilComplete() } func handleMessageSent() { @@ -1103,7 +1097,7 @@ extension ConversationVC: UIDocumentInteractionControllerDelegate { extension ConversationVC { - fileprivate func approveMessageRequestIfNeeded(for thread: TSThread?, with transaction: YapDatabaseReadWriteTransaction, isNewThread: Bool, timestamp: UInt64) -> Promise { + fileprivate func approveMessageRequestIfNeeded(for thread: TSThread?, isNewThread: Bool, timestamp: UInt64) -> Promise { guard let contactThread: TSContactThread = thread as? TSContactThread else { return Promise.value(()) } // If the contact doesn't exist then we should create it so we can store the 'isApproved' state @@ -1134,7 +1128,17 @@ extension ConversationVC { } return promise - .then { MessageSender.sendNonDurably(messageRequestResponse, in: contactThread, using: transaction) } + .then { _ -> Promise in + let (promise, seal) = Promise.pending() + Storage.writeSync { transaction in + MessageSender.sendNonDurably(messageRequestResponse, in: contactThread, using: transaction) + .done { seal.fulfill(()) } + .catch { _ in seal.fulfill(()) } // Fulfill even if this failed; the configuration in the swarm should be at most 2 days old + .retainUntilComplete() + } + + return promise + } .map { _ in if self?.presentedViewController is ModalActivityIndicatorViewController { self?.dismiss(animated: true, completion: nil) // Dismiss the loader @@ -1143,9 +1147,11 @@ extension ConversationVC { } .map { _ in // Default 'didApproveMe' to true for the person approving the message request - contact.isApproved = true - contact.didApproveMe = (contact.didApproveMe || !isNewThread) - Storage.shared.setContact(contact, using: transaction) + Storage.write { transaction in + contact.isApproved = true + contact.didApproveMe = (contact.didApproveMe || !isNewThread) + Storage.shared.setContact(contact, using: transaction) + } // Hide the 'messageRequestView' since the request has been approved and force a config // sync to propagate the contact approval state (both must run on the main thread) @@ -1183,30 +1189,27 @@ extension ConversationVC { // Send a sync message with the details of the contact if let appDelegate = UIApplication.shared.delegate as? AppDelegate { - appDelegate.forceSyncConfigurationNowIfNeeded(with: transaction).retainUntilComplete() + appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() } } } } @objc func acceptMessageRequest() { - Storage.write { transaction in - let promise: Promise = self.approveMessageRequestIfNeeded( - for: self.thread, - with: transaction, - isNewThread: false, - timestamp: NSDate.millisecondTimestamp() - ) - - // Show an error indicating that approving the thread failed - promise.catch(on: DispatchQueue.main) { [weak self] _ in - let alert = UIAlertController(title: "Session", message: NSLocalizedString("MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE", comment: ""), preferredStyle: .alert) - alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil)) - self?.present(alert, animated: true, completion: nil) - } - - promise.retainUntilComplete() + let promise: Promise = self.approveMessageRequestIfNeeded( + for: self.thread, + isNewThread: false, + timestamp: NSDate.millisecondTimestamp() + ) + + // Show an error indicating that approving the thread failed + promise.catch(on: DispatchQueue.main) { [weak self] _ in + let alert = UIAlertController(title: "Session", message: NSLocalizedString("MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE", comment: ""), preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil)) + self?.present(alert, animated: true, completion: nil) } + + promise.retainUntilComplete() } @objc func deleteMessageRequest() { diff --git a/Session/Conversations/Message Cells/Content Views/LinkPreviewView.swift b/Session/Conversations/Message Cells/Content Views/LinkPreviewView.swift index a2fe25fc5..2132d417b 100644 --- a/Session/Conversations/Message Cells/Content Views/LinkPreviewView.swift +++ b/Session/Conversations/Message Cells/Content Views/LinkPreviewView.swift @@ -9,7 +9,7 @@ final class LinkPreviewView : UIView { private lazy var imageViewContainerHeightConstraint = imageView.set(.height, to: 100) private lazy var sentLinkPreviewTextColor: UIColor = { - let isOutgoing = (viewItem!.interaction.interactionType() == .outgoingMessage) + let isOutgoing = (viewItem?.interaction.interactionType() == .outgoingMessage) switch (isOutgoing, AppModeManager.shared.currentAppMode) { case (true, .dark), (false, .light): return .black case (true, .light): return Colors.grey diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index da9153682..61261dad7 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -7,7 +7,18 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv private var threadViewModelCache: [String:ThreadViewModel] = [:] // Thread ID to ThreadViewModel private var tableViewTopConstraint: NSLayoutConstraint! private var unreadMessageRequestCount: UInt { - OWSMessageUtils.sharedManager().unreadMessageRequestCount() + var count: UInt = 0 + + dbConnection.read { transaction in + let ext = transaction.ext(TSThreadDatabaseViewExtensionName) as! YapDatabaseViewTransaction + ext.enumerateRows(inGroup: TSMessageRequestGroup) { _, _, object, _, _, _ in + if ((object as? TSThread)?.unreadMessageCount(transaction: transaction) ?? 0) > 0 { + count += 1 + } + } + } + + return count } private var threadCount: UInt { @@ -84,6 +95,12 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv // MARK: Lifecycle override func viewDidLoad() { super.viewDidLoad() + + // Note: This is a hack to ensure `isRTL` is initially gets run on the main thread so the value is cached (it gets + // called on background threads and if it hasn't cached the value then it can cause odd performance issues since + // it accesses UIKit) + _ = CurrentAppContext().isRTL + // Threads (part 1) dbConnection.beginLongLivedReadTransaction() // Freeze the connection for use on the main thread (this gives us a stable data source that doesn't change until we tell it to) // Preparation diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 30525183d..6f1182807 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -7,10 +7,11 @@ extension AppDelegate { guard Storage.shared.getUser()?.name != nil else { return } let userDefaults = UserDefaults.standard let lastSync = userDefaults[.lastConfigurationSync] ?? .distantPast - guard Date().timeIntervalSince(lastSync) > 7 * 24 * 60 * 60, - let configurationMessage = ConfigurationMessage.getCurrent() else { return } // Sync every 2 days + guard Date().timeIntervalSince(lastSync) > 7 * 24 * 60 * 60 else { return } // Sync every 2 days let destination = Message.Destination.contact(publicKey: getUserHexEncodedPublicKey()) - Storage.shared.write { transaction in + Storage.write { transaction in + guard let configurationMessage = ConfigurationMessage.getCurrent(with: transaction) else { return } + let job = MessageSendJob(message: configurationMessage, destination: destination) JobQueue.shared.add(job, using: transaction) } @@ -22,14 +23,17 @@ extension AppDelegate { } } - func forceSyncConfigurationNowIfNeeded(with transaction: YapDatabaseReadWriteTransaction? = nil) -> Promise { - guard Storage.shared.getUser()?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent(with: transaction) else { - return Promise.value(()) - } - + func forceSyncConfigurationNowIfNeeded() -> Promise { let destination = Message.Destination.contact(publicKey: getUserHexEncodedPublicKey()) let (promise, seal) = Promise.pending() + + // Note: SQLite only supports a single write thread so we can be sure this will retrieve the most up-to-date data Storage.writeSync { transaction in + guard Storage.shared.getUser(using: transaction)?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent(with: transaction) else { + seal.fulfill(()) + return + } + MessageSender.send(configurationMessage, to: destination, using: transaction).done { seal.fulfill(()) }.catch { _ in diff --git a/Session/Meta/MainAppContext.m b/Session/Meta/MainAppContext.m index 66948daf6..2fad58d41 100644 --- a/Session/Meta/MainAppContext.m +++ b/Session/Meta/MainAppContext.m @@ -166,6 +166,7 @@ NSString *const ReportedApplicationStateDidChangeNotification = @"ReportedApplic - (BOOL)isRTL { + // FIXME: We should try to remove this as we've had to add a hack to ensure the first call to this runs on the main thread static BOOL isRTL = NO; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ diff --git a/Session/Settings/NukeDataModal.swift b/Session/Settings/NukeDataModal.swift index e3110969d..dba878a22 100644 --- a/Session/Settings/NukeDataModal.swift +++ b/Session/Settings/NukeDataModal.swift @@ -128,7 +128,7 @@ final class NukeDataModal : Modal { appDelegate.forceSyncConfigurationNowIfNeeded().ensure(on: DispatchQueue.main) { self?.dismiss(animated: true, completion: nil) // Dismiss the loader UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later - General.Cache.cachedEncodedPublicKey = nil // Remove the cached key so it gets re-cached on next access + General.Cache.cachedEncodedPublicKey.mutate { $0 = nil } // Remove the cached key so it gets re-cached on next access NotificationCenter.default.post(name: .dataNukeRequested, object: nil) }.retainUntilComplete() } @@ -140,7 +140,7 @@ final class NukeDataModal : Modal { self?.dismiss(animated: true, completion: nil) // Dismiss the loader let potentiallyMaliciousSnodes = confirmations.compactMap { $0.value == false ? $0.key : nil } if potentiallyMaliciousSnodes.isEmpty { - General.Cache.cachedEncodedPublicKey = nil // Remove the cached key so it gets re-cached on next access + General.Cache.cachedEncodedPublicKey.mutate { $0 = nil } // Remove the cached key so it gets re-cached on next access UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later NotificationCenter.default.post(name: .dataNukeRequested, object: nil) } else { diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index 0d67c90fe..ff31d0965 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -42,7 +42,7 @@ public final class BackgroundPoller : NSObject { guard let snode = swarm.randomElement() else { throw SnodeAPI.Error.generic } return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) { return SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey).then(on: DispatchQueue.main) { rawResponse -> Promise in - let messages = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) + let (messages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) let promises = messages.compactMap { json -> Promise? in // Use a best attempt approach here; we don't want to fail the entire process if one of the // messages failed to parse. @@ -51,6 +51,10 @@ public final class BackgroundPoller : NSObject { let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: true) return job.execute() } + + // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value + SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, associatedWith: publicKey, from: lastRawMessage) + return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects } } diff --git a/SessionMessagingKit/Database/Storage+ClosedGroups.swift b/SessionMessagingKit/Database/Storage+ClosedGroups.swift index 05e9fc1a5..a21d6d551 100644 --- a/SessionMessagingKit/Database/Storage+ClosedGroups.swift +++ b/SessionMessagingKit/Database/Storage+ClosedGroups.swift @@ -10,13 +10,19 @@ extension Storage { private static let closedGroupZombieMembersCollection = "SNClosedGroupZombieMembersCollection" public func getClosedGroupEncryptionKeyPairs(for groupPublicKey: String) -> [ECKeyPair] { + var result: [ECKeyPair] = [] + Storage.read { transaction in + result = self.getClosedGroupEncryptionKeyPairs(for: groupPublicKey, using: transaction) + } + return result + } + + public func getClosedGroupEncryptionKeyPairs(for groupPublicKey: String, using transaction: YapDatabaseReadTransaction) -> [ECKeyPair] { let collection = Storage.getClosedGroupEncryptionKeyPairCollection(for: groupPublicKey) var timestampsAndKeyPairs: [(timestamp: Double, keyPair: ECKeyPair)] = [] - Storage.read { transaction in - transaction.enumerateKeysAndObjects(inCollection: collection) { key, object, _ in - guard let timestamp = Double(key), let keyPair = object as? ECKeyPair else { return } - timestampsAndKeyPairs.append((timestamp, keyPair)) - } + transaction.enumerateKeysAndObjects(inCollection: collection) { key, object, _ in + guard let timestamp = Double(key), let keyPair = object as? ECKeyPair else { return } + timestampsAndKeyPairs.append((timestamp, keyPair)) } return timestampsAndKeyPairs.sorted { $0.timestamp < $1.timestamp }.map { $0.keyPair } } @@ -24,6 +30,10 @@ extension Storage { public func getLatestClosedGroupEncryptionKeyPair(for groupPublicKey: String) -> ECKeyPair? { return getClosedGroupEncryptionKeyPairs(for: groupPublicKey).last } + + public func getLatestClosedGroupEncryptionKeyPair(for groupPublicKey: String, using transaction: YapDatabaseReadTransaction) -> ECKeyPair? { + return getClosedGroupEncryptionKeyPairs(for: groupPublicKey, using: transaction).last + } public func addClosedGroupEncryptionKeyPair(_ keyPair: ECKeyPair, for groupPublicKey: String, using transaction: Any) { let collection = Storage.getClosedGroupEncryptionKeyPairCollection(for: groupPublicKey) @@ -39,10 +49,14 @@ extension Storage { public func getUserClosedGroupPublicKeys() -> Set { var result: Set = [] Storage.read { transaction in - result = Set(transaction.allKeys(inCollection: Storage.closedGroupPublicKeyCollection)) + result = self.getUserClosedGroupPublicKeys(using: transaction) } return result } + + public func getUserClosedGroupPublicKeys(using transaction: YapDatabaseReadTransaction) -> Set { + return Set(transaction.allKeys(inCollection: Storage.closedGroupPublicKeyCollection)) + } public func addClosedGroupPublicKey(_ groupPublicKey: String, using transaction: Any) { (transaction as! YapDatabaseReadWriteTransaction).setObject(groupPublicKey, forKey: groupPublicKey, inCollection: Storage.closedGroupPublicKeyCollection) @@ -81,4 +95,8 @@ extension Storage { public func isClosedGroup(_ publicKey: String) -> Bool { getUserClosedGroupPublicKeys().contains(publicKey) } + + public func isClosedGroup(_ publicKey: String, using transaction: YapDatabaseReadTransaction) -> Bool { + getUserClosedGroupPublicKeys(using: transaction).contains(publicKey) + } } diff --git a/SessionMessagingKit/Database/Storage+OpenGroups.swift b/SessionMessagingKit/Database/Storage+OpenGroups.swift index 5ea663550..ddcbc5268 100644 --- a/SessionMessagingKit/Database/Storage+OpenGroups.swift +++ b/SessionMessagingKit/Database/Storage+OpenGroups.swift @@ -152,8 +152,31 @@ extension Storage { let key = "\(server).\(room)" (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: key, inCollection: collection) } + + // MARK: - OpenGroupServerIdToUniqueIdLookup + public static let openGroupServerIdToUniqueIdLookupCollection = "SNOpenGroupServerIdToUniqueIdLookup" + public func getOpenGroupServerIdLookup(_ serverId: UInt64, in room: String, on server: String, using transaction: YapDatabaseReadTransaction) -> OpenGroupServerIdLookup? { + let key: String = OpenGroupServerIdLookup.id(serverId: serverId, in: room, on: server) + return transaction.object(forKey: key, inCollection: Storage.openGroupServerIdToUniqueIdLookupCollection) as? OpenGroupServerIdLookup + } + + public func addOpenGroupServerIdLookup(_ serverId: UInt64?, tsMessageId: String?, in room: String, on server: String, using transaction: YapDatabaseReadWriteTransaction) { + guard let serverId: UInt64 = serverId, let tsMessageId: String = tsMessageId else { return } + + let lookup: OpenGroupServerIdLookup = OpenGroupServerIdLookup(server: server, room: room, serverId: serverId, tsMessageId: tsMessageId) + addOpenGroupServerIdLookup(lookup, using: transaction) + } + + public func addOpenGroupServerIdLookup(_ lookup: OpenGroupServerIdLookup, using transaction: YapDatabaseReadWriteTransaction) { + transaction.setObject(lookup, forKey: lookup.id, inCollection: Storage.openGroupServerIdToUniqueIdLookupCollection) + } + + public func removeOpenGroupServerIdLookup(_ serverId: UInt64, in room: String, on server: String, using transaction: YapDatabaseReadWriteTransaction) { + let key: String = OpenGroupServerIdLookup.id(serverId: serverId, in: room, on: server) + transaction.removeObject(forKey: key, inCollection: Storage.openGroupServerIdToUniqueIdLookupCollection) + } // MARK: - Metadata diff --git a/SessionMessagingKit/Database/Storage+Shared.swift b/SessionMessagingKit/Database/Storage+Shared.swift index b2752d29d..d8f5de95d 100644 --- a/SessionMessagingKit/Database/Storage+Shared.swift +++ b/SessionMessagingKit/Database/Storage+Shared.swift @@ -36,11 +36,21 @@ extension Storage { } @objc public func getUser() -> Contact? { - guard let userPublicKey = getUserPublicKey() else { return nil } + return getUser(using: nil) + } + + public func getUser(using transaction: YapDatabaseReadTransaction?) -> Contact? { + let userPublicKey = getUserHexEncodedPublicKey() var result: Contact? - Storage.read { transaction in + + if let transaction = transaction { result = Storage.shared.getContact(with: userPublicKey, using: transaction) } + else { + Storage.read { transaction in + result = Storage.shared.getContact(with: userPublicKey, using: transaction) + } + } return result } } diff --git a/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift b/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift index d08ef570e..da328bd28 100644 --- a/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift +++ b/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift @@ -2,9 +2,9 @@ import SessionUtilitiesKit extension ConfigurationMessage { - public static func getCurrent(with transaction: YapDatabaseReadWriteTransaction? = nil) -> ConfigurationMessage? { + public static func getCurrent(with transaction: YapDatabaseReadTransaction) -> ConfigurationMessage? { let storage = Storage.shared - guard let user = storage.getUser() else { return nil } + guard let user = storage.getUser(using: transaction) else { return nil } let displayName = user.name let profilePictureURL = user.profilePictureURL @@ -13,91 +13,84 @@ extension ConfigurationMessage { var openGroups: Set = [] var contacts: Set = [] - let populateDataClosure: (YapDatabaseReadTransaction) -> () = { transaction in - TSGroupThread.enumerateCollectionObjects(with: transaction) { object, _ in - guard let thread = object as? TSGroupThread else { return } - - switch thread.groupModel.groupType { - case .closedGroup: - guard thread.isCurrentUserMemberInGroup() else { return } - - let groupID = thread.groupModel.groupId - let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID) - - guard storage.isClosedGroup(groupPublicKey), let encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey) else { - return - } - - let closedGroup = ClosedGroup( - publicKey: groupPublicKey, - name: thread.groupModel.groupName!, - encryptionKeyPair: encryptionKeyPair, - members: Set(thread.groupModel.groupMemberIds), - admins: Set(thread.groupModel.groupAdminIds), - expirationTimer: thread.disappearingMessagesDuration(with: transaction) - ) - closedGroups.insert(closedGroup) - - case .openGroup: - if let v2OpenGroup = storage.getV2OpenGroup(for: thread.uniqueId!) { - openGroups.insert("\(v2OpenGroup.server)/\(v2OpenGroup.room)?public_key=\(v2OpenGroup.publicKey)") - } - - default: break - } - } + TSGroupThread.enumerateCollectionObjects(with: transaction) { object, _ in + guard let thread = object as? TSGroupThread else { return } - let currentUserPublicKey: String = getUserHexEncodedPublicKey() - - contacts = storage.getAllContacts(with: transaction) - .filter { contact -> Bool in - let threadID = TSContactThread.threadID(fromContactSessionID: contact.sessionID) + switch thread.groupModel.groupType { + case .closedGroup: + guard thread.isCurrentUserMemberInGroup() else { return } - return ( - // Skip the current user - contact.sessionID != currentUserPublicKey && - // Contacts which have visible threads - TSContactThread.fetch(uniqueId: threadID, transaction: transaction)?.shouldBeVisible == true && ( - - // Include already approved contacts - contact.isApproved || - contact.didApproveMe || - - // Sync blocked contacts - SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(contact.sessionID) - ) - ) - } - .map { contact -> ConfigurationMessage.Contact in - // Can just default the 'hasX' values to true as they will be set to this - // when converting to proto anyway - let profilePictureURL = contact.profilePictureURL - let profileKey = contact.profileEncryptionKey?.keyData + let groupID = thread.groupModel.groupId + let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID) - return ConfigurationMessage.Contact( - publicKey: contact.sessionID, - displayName: (contact.name ?? contact.sessionID), - profilePictureURL: profilePictureURL, - profileKey: profileKey, - hasIsApproved: true, - isApproved: contact.isApproved, - hasIsBlocked: true, - isBlocked: SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(contact.sessionID), - hasDidApproveMe: true, - didApproveMe: contact.didApproveMe + guard + storage.isClosedGroup(groupPublicKey, using: transaction), + let encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey, using: transaction) + else { + return + } + + let closedGroup = ClosedGroup( + publicKey: groupPublicKey, + name: (thread.groupModel.groupName ?? ""), + encryptionKeyPair: encryptionKeyPair, + members: Set(thread.groupModel.groupMemberIds), + admins: Set(thread.groupModel.groupAdminIds), + expirationTimer: thread.disappearingMessagesDuration(with: transaction) ) - } - .asSet() + closedGroups.insert(closedGroup) + + case .openGroup: + if let threadId: String = thread.uniqueId, let v2OpenGroup = storage.getV2OpenGroup(for: threadId) { + openGroups.insert("\(v2OpenGroup.server)/\(v2OpenGroup.room)?public_key=\(v2OpenGroup.publicKey)") + } + + default: break } + } - // If we are provided with a transaction then read the data based on the state of the database - // from within the transaction rather than the state in disk - if let transaction: YapDatabaseReadWriteTransaction = transaction { - populateDataClosure(transaction) - } - else { - Storage.read { transaction in populateDataClosure(transaction) } - } + let currentUserPublicKey: String = getUserHexEncodedPublicKey() + + contacts = storage.getAllContacts(with: transaction) + .compactMap { contact -> ConfigurationMessage.Contact? in + let threadID = TSContactThread.threadID(fromContactSessionID: contact.sessionID) + + guard + // Skip the current user + contact.sessionID != currentUserPublicKey && + // Contacts which have visible threads + TSContactThread.fetch(uniqueId: threadID, transaction: transaction)?.shouldBeVisible == true && ( + + // Include already approved contacts + contact.isApproved || + contact.didApproveMe || + + // Sync blocked contacts + SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(contact.sessionID) + ) + else { + return nil + } + + // Can just default the 'hasX' values to true as they will be set to this + // when converting to proto anyway + let profilePictureURL = contact.profilePictureURL + let profileKey = contact.profileEncryptionKey?.keyData + + return ConfigurationMessage.Contact( + publicKey: contact.sessionID, + displayName: (contact.name ?? contact.sessionID), + profilePictureURL: profilePictureURL, + profileKey: profileKey, + hasIsApproved: true, + isApproved: contact.isApproved, + hasIsBlocked: true, + isBlocked: SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(contact.sessionID), + hasDidApproveMe: true, + didApproveMe: contact.didApproveMe + ) + } + .asSet() return ConfigurationMessage( displayName: displayName, diff --git a/SessionMessagingKit/Open Groups/OpenGroupServerIdLookup.swift b/SessionMessagingKit/Open Groups/OpenGroupServerIdLookup.swift new file mode 100644 index 000000000..ee79614af --- /dev/null +++ b/SessionMessagingKit/Open Groups/OpenGroupServerIdLookup.swift @@ -0,0 +1,46 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +@objc(SNOpenGroupServerIdLookup) +public final class OpenGroupServerIdLookup: NSObject, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility + @objc public let id: String + @objc public let serverId: UInt64 + @objc public let tsMessageId: String + + // MARK: - Initialization + + @objc public init(server: String, room: String, serverId: UInt64, tsMessageId: String) { + self.id = OpenGroupServerIdLookup.id(serverId: serverId, in: room, on: server) + self.serverId = serverId + self.tsMessageId = tsMessageId + + super.init() + } + + private override init() { preconditionFailure("Use init(blindedId:sessionId:) instead.") } + + // MARK: - Coding + + public required init?(coder: NSCoder) { + guard let id: String = coder.decodeObject(forKey: "id") as! String? else { return nil } + guard let serverId: UInt64 = coder.decodeObject(forKey: "serverId") as! UInt64? else { return nil } + guard let tsMessageId: String = coder.decodeObject(forKey: "tsMessageId") as! String? else { return nil } + + self.id = id + self.serverId = serverId + self.tsMessageId = tsMessageId + } + + public func encode(with coder: NSCoder) { + coder.encode(id, forKey: "id") + coder.encode(serverId, forKey: "serverId") + coder.encode(tsMessageId, forKey: "tsMessageId") + } + + // MARK: - Convenience + + static func id(serverId: UInt64, in room: String, on server: String) -> String { + return "\(server).\(room).\(serverId)" + } +} diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index ab23a33f0..8b4acf3ae 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -219,9 +219,15 @@ extension MessageReceiver { // Note: We only update these values if the proto actually has values for them (this is to // prevent an edge case where an old client could override the values with default values // since they aren't included) - if contactInfo.hasIsApproved { contact.isApproved = contactInfo.isApproved } + // + // Note: Since message requests has no reverse, the only case we need to process is a + // config message setting *isApproved* and *didApproveMe* to true. This may prevent some + // weird edge cases where a config message swapping *isApproved* and *didApproveMe* to + // false. + if contactInfo.hasIsApproved && contactInfo.isApproved { contact.isApproved = true } + if contactInfo.hasDidApproveMe && contactInfo.didApproveMe { contact.didApproveMe = true } + if contactInfo.hasIsBlocked { contact.isBlocked = contactInfo.isBlocked } - if contactInfo.hasDidApproveMe { contact.didApproveMe = contactInfo.didApproveMe } Storage.shared.setContact(contact, using: transaction) @@ -385,7 +391,15 @@ extension MessageReceiver { } if let tsMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) { // Keep track of the open group server message ID ↔ message ID relationship - if let serverID = message.openGroupServerMessageID { tsMessage.openGroupServerMessageID = serverID } + if let serverID = message.openGroupServerMessageID { + tsMessage.openGroupServerMessageID = serverID + + // Create a lookup between the openGroupServerMessageId and the tsMessage id for easy lookup + if let openGroup: OpenGroupV2 = storage.getV2OpenGroup(for: threadID) { + storage.addOpenGroupServerIdLookup(serverID, tsMessageId: tsMessageID, in: openGroup.room, on: openGroup.server, using: transaction) + } + } + // Keep track of server hash if let serverHash = message.serverHash { tsMessage.serverHash = serverHash } tsMessage.save(with: transaction) @@ -827,12 +841,14 @@ extension MessageReceiver { // a new configuration message (otherwise the `contact` will be loaded direct from the database and the // `didApproveMe` value won't have been updated) DispatchQueue.global(qos: .background).async { - guard Storage.shared.getUser()?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent() else { - return + Storage.write { transaction in + guard Storage.shared.getUser()?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent(with: transaction) else { + return + } + + let destination: Message.Destination = Message.Destination.contact(publicKey: userPublicKey) + MessageSender.send(configurationMessage, to: destination, using: transaction).retainUntilComplete() } - - let destination: Message.Destination = Message.Destination.contact(publicKey: userPublicKey) - MessageSender.send(configurationMessage, to: destination, using: transaction).retainUntilComplete() } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index dd44c8197..c02ef9088 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -313,6 +313,7 @@ public final class MessageSender : NSObject { base64EncodedData: plaintext.base64EncodedString(), base64EncodedSignature: nil) OpenGroupAPIV2.send(openGroupMessage, to: room, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { openGroupMessage in message.openGroupServerMessageID = given(openGroupMessage.serverID) { UInt64($0) } + storage.write(with: { transaction in MessageSender.handleSuccessfulMessageSend(message, to: destination, serverTimestamp: openGroupMessage.sentTimestamp, using: transaction) seal.fulfill(()) @@ -341,6 +342,20 @@ public final class MessageSender : NSObject { // Otherwise the quote messages may not be able // to be found by the timestamp on other devices tsMessage.updateOpenGroupServerID(openGroupServerMessageID, serverTimeStamp: timestamp) + + // Create a lookup between the openGroupServerMessageId and the tsMessage id for easy lookup + switch destination { + case .openGroupV2(let room, let server): + Storage.shared.addOpenGroupServerIdLookup( + openGroupServerMessageID, + tsMessageId: tsMessage.uniqueId, + in: room, + on: server, + using: transaction + ) + + default: break + } } // Mark the message as sent var recipients = [ message.recipient! ] diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index eb5966cd5..1644488e6 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -100,15 +100,17 @@ public final class ClosedGroupPoller : NSObject { private func poll(_ groupPublicKey: String) -> Promise { guard isPolling(for: groupPublicKey) else { return Promise.value(()) } - let promise = SnodeAPI.getSwarm(for: groupPublicKey).then2 { [weak self] swarm -> Promise<[JSON]> in + let promise = SnodeAPI.getSwarm(for: groupPublicKey).then2 { [weak self] swarm -> Promise<(Snode, [JSON], JSON?)> in // randomElement() uses the system's default random generator, which is cryptographically secure guard let snode = swarm.randomElement() else { return Promise(error: Error.insufficientSnodes) } guard let self = self, self.isPolling(for: groupPublicKey) else { return Promise(error: Error.pollingCanceled) } return SnodeAPI.getRawMessages(from: snode, associatedWith: groupPublicKey).map2 { - SnodeAPI.parseRawMessagesResponse($0, from: snode, associatedWith: groupPublicKey) + let (rawMessages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse($0, from: snode, associatedWith: groupPublicKey) + + return (snode, rawMessages, lastRawMessage) } } - promise.done2 { [weak self] rawMessages in + promise.done2 { [weak self] snode, rawMessages, lastRawMessage in guard let self = self, self.isPolling(for: groupPublicKey) else { return } if !rawMessages.isEmpty { SNLog("Received \(rawMessages.count) new message(s) in closed group with public key: \(groupPublicKey).") @@ -125,6 +127,9 @@ public final class ClosedGroupPoller : NSObject { SNLog("Failed to deserialize envelope due to error: \(error).") } } + + // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value + SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, associatedWith: groupPublicKey, from: lastRawMessage) } promise.catch2 { error in SNLog("Polling failed for closed group with public key: \(groupPublicKey) due to error: \(error).") diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift index a1b3a47b3..930361510 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift @@ -82,6 +82,7 @@ public final class OpenGroupPollerV2 : NSObject { } } } + // - Moderators if var x = OpenGroupAPIV2.moderators[server] { x[body.room] = Set(body.moderators) @@ -89,18 +90,23 @@ public final class OpenGroupPollerV2 : NSObject { } else { OpenGroupAPIV2.moderators[server] = [ body.room : Set(body.moderators) ] } + // - Deletions + guard !body.deletions.isEmpty else { return } + 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) + guard let transaction: YapDatabaseReadWriteTransaction = transaction as? YapDatabaseReadWriteTransaction else { return } + + deletedMessageServerIDs.forEach { openGroupServerMessageId in + guard let messageLookup: OpenGroupServerIdLookup = storage.getOpenGroupServerIdLookup(openGroupServerMessageId, in: body.room, on: self.server, using: transaction) else { + return + } + guard let tsMessage: TSMessage = TSMessage.fetch(uniqueId: messageLookup.tsMessageId, transaction: transaction) else { return } + + tsMessage.remove(with: transaction) + storage.removeOpenGroupServerIdLookup(openGroupServerMessageId, in: body.room, on: self.server, using: transaction) } - messagesToRemove.forEach { $0.remove(with: transaction) } } } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 623acf1ff..5c9e66561 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -94,7 +94,7 @@ public final class Poller : NSObject { let userPublicKey = getUserHexEncodedPublicKey() return SnodeAPI.getRawMessages(from: snode, associatedWith: userPublicKey).then(on: Threading.pollerQueue) { [weak self] rawResponse -> Promise in guard let strongSelf = self, strongSelf.isPolling else { return Promise { $0.fulfill(()) } } - let messages = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: userPublicKey) + let (messages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: userPublicKey) if !messages.isEmpty { SNLog("Received \(messages.count) new message(s).") } @@ -110,6 +110,10 @@ public final class Poller : NSObject { SNLog("Failed to deserialize envelope due to error: \(error).") } } + + // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value + SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, associatedWith: userPublicKey, from: lastRawMessage) + strongSelf.pollCount += 1 if strongSelf.pollCount == Poller.maxPollCount { throw Error.pollLimitReached diff --git a/SessionMessagingKit/Storage.swift b/SessionMessagingKit/Storage.swift index 127cce708..6de688097 100644 --- a/SessionMessagingKit/Storage.swift +++ b/SessionMessagingKit/Storage.swift @@ -17,15 +17,18 @@ public protocol SessionMessagingKitStorageProtocol { func getUserKeyPair() -> ECKeyPair? func getUserED25519KeyPair() -> Box.KeyPair? func getUser() -> Contact? + func getUser(using transaction: YapDatabaseReadTransaction?) -> Contact? func getAllContacts() -> Set func getAllContacts(with transaction: YapDatabaseReadTransaction) -> Set // MARK: - Closed Groups func getUserClosedGroupPublicKeys() -> Set + func getUserClosedGroupPublicKeys(using transaction: YapDatabaseReadTransaction) -> Set func getZombieMembers(for groupPublicKey: String) -> Set func setZombieMembers(for groupPublicKey: String, to zombies: Set, using transaction: Any) func isClosedGroup(_ publicKey: String) -> Bool + func isClosedGroup(_ publicKey: String, using transaction: YapDatabaseReadTransaction) -> Bool // MARK: - Jobs @@ -67,6 +70,13 @@ public protocol SessionMessagingKitStorageProtocol { func getLastDeletionServerID(for room: String, on server: String) -> Int64? func setLastDeletionServerID(for room: String, on server: String, to newValue: Int64, using transaction: Any) func removeLastDeletionServerID(for room: String, on server: String, using transaction: Any) + + // MARK: - OpenGroupServerIdToUniqueIdLookup + + func getOpenGroupServerIdLookup(_ serverId: UInt64, in room: String, on server: String, using transaction: YapDatabaseReadTransaction) -> OpenGroupServerIdLookup? + func addOpenGroupServerIdLookup(_ serverId: UInt64?, tsMessageId: String?, in room: String, on server: String, using transaction: YapDatabaseReadWriteTransaction) + func addOpenGroupServerIdLookup(_ lookup: OpenGroupServerIdLookup, using transaction: YapDatabaseReadWriteTransaction) + func removeOpenGroupServerIdLookup(_ serverId: UInt64, in room: String, on server: String, using transaction: YapDatabaseReadWriteTransaction) // MARK: - Open Group Metadata diff --git a/SessionMessagingKit/Utilities/General.swift b/SessionMessagingKit/Utilities/General.swift index 565338f65..d2e4e0c96 100644 --- a/SessionMessagingKit/Utilities/General.swift +++ b/SessionMessagingKit/Utilities/General.swift @@ -2,7 +2,7 @@ import Foundation public enum General { public enum Cache { - public static var cachedEncodedPublicKey: String? = nil + public static var cachedEncodedPublicKey: Atomic = Atomic(nil) } } @@ -14,10 +14,10 @@ public class GeneralUtilities: NSObject { } public func getUserHexEncodedPublicKey() -> String { - if let cachedKey: String = General.Cache.cachedEncodedPublicKey { return cachedKey } + if let cachedKey: String = General.Cache.cachedEncodedPublicKey.wrappedValue { return cachedKey } if let keyPair = OWSIdentityManager.shared().identityKeyPair() { // Can be nil under some circumstances - General.Cache.cachedEncodedPublicKey = keyPair.hexEncodedPublicKey + General.Cache.cachedEncodedPublicKey.mutate { $0 = keyPair.hexEncodedPublicKey } return keyPair.hexEncodedPublicKey } diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index 0e0ff5db3..91a193a7e 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -162,7 +162,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView message.sentTimestamp = NSDate.millisecondTimestamp() message.text = (isSharingUrl && (messageText?.isEmpty == true || attachments[0].linkPreviewDraft == nil) ? ( - (messageText?.isEmpty == true ? + (messageText?.isEmpty == true || (attachments[0].text() == messageText) ? attachments[0].text() : "\(attachments[0].text() ?? "")\n\n\(messageText ?? "")" ) diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index 7602242bf..ae1382e67 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -398,20 +398,6 @@ public final class SnodeAPI : NSObject { return promise } - public static func getMessages(for publicKey: String) -> Promise> { - let (promise, seal) = Promise>.pending() - Threading.workQueue.async { - attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { - getTargetSnodes(for: publicKey).mapValues2 { targetSnode in - return getMessagesInternal(from: targetSnode, associatedWith: publicKey).map2 { rawResponse in - parseRawMessagesResponse(rawResponse, from: targetSnode, associatedWith: publicKey) - } - }.map2 { Set($0) } - }.done2 { seal.fulfill($0) }.catch2 { seal.reject($0) } - } - return promise - } - private static func getMessagesInternal(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { let storage = SNSnodeKitConfiguration.shared.storage @@ -573,20 +559,23 @@ public final class SnodeAPI : NSObject { }) } - public static func parseRawMessagesResponse(_ rawResponse: Any, from snode: Snode, associatedWith publicKey: String) -> [JSON] { - guard let json = rawResponse as? JSON, let rawMessages = json["messages"] as? [JSON] else { return [] } - updateLastMessageHashValueIfPossible(for: snode, associatedWith: publicKey, from: rawMessages) - return removeDuplicates(from: rawMessages, associatedWith: publicKey) + public static func parseRawMessagesResponse(_ rawResponse: Any, from snode: Snode, associatedWith publicKey: String) -> (messages: [JSON], lastRawMessage: JSON?) { + guard let json = rawResponse as? JSON, let rawMessages = json["messages"] as? [JSON] else { return ([], nil) } + + return ( + removeDuplicates(from: rawMessages, associatedWith: publicKey), + rawMessages.last + ) } - private static func updateLastMessageHashValueIfPossible(for snode: Snode, associatedWith publicKey: String, from rawMessages: [JSON]) { - if let lastMessage = rawMessages.last, let lastHash = lastMessage["hash"] as? String, let expirationDate = lastMessage["expiration"] as? UInt64 { + public static func updateLastMessageHashValueIfPossible(for snode: Snode, associatedWith publicKey: String, from lastRawMessage: JSON?) { + if let lastMessage = lastRawMessage, let lastHash = lastMessage["hash"] as? String, let expirationDate = lastMessage["expiration"] as? UInt64 { SNSnodeKitConfiguration.shared.storage.writeSync { transaction in SNSnodeKitConfiguration.shared.storage.setLastMessageHashInfo(for: snode, associatedWith: publicKey, to: [ "hash" : lastHash, "expirationDate" : NSNumber(value: expirationDate) ], using: transaction) } - } else if (!rawMessages.isEmpty) { - SNLog("Failed to update last message hash value from: \(rawMessages).") + } else if (lastRawMessage != nil) { + SNLog("Failed to update last message hash value from: \(String(describing: lastRawMessage)).") } } diff --git a/SessionUtilitiesKit/General/Atomic.swift b/SessionUtilitiesKit/General/Atomic.swift new file mode 100644 index 000000000..14baeed68 --- /dev/null +++ b/SessionUtilitiesKit/General/Atomic.swift @@ -0,0 +1,44 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +import Foundation + +// MARK: - Atomic +/// The `Atomic` wrapper is a generic wrapper providing a thread-safe way to get and set a value +/// +/// A write-up on the need for this class and it's approach can be found here: +/// https://www.vadimbulavin.com/swift-atomic-properties-with-property-wrappers/ +/// there is also another approach which can be taken but it requires separate types for collections and results in +/// a somewhat inconsistent interface between different `Atomic` wrappers +@propertyWrapper +public class Atomic { + private let queue: DispatchQueue = DispatchQueue(label: "io.oxen.\(UUID().uuidString)") + private var value: Value + + /// In order to change the value you **must** use the `mutate` function + public var wrappedValue: Value { + return queue.sync { return value } + } + + /// For more information see https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#projections + public var projectedValue: Atomic { + return self + } + + // MARK: - Initialization + public init(_ initialValue: Value) { + self.value = initialValue + } + + // MARK: - Functions + + public func mutate(_ mutation: (inout Value) -> Void) { + return queue.sync { + mutation(&value) + } + } +} + +extension Atomic where Value: CustomDebugStringConvertible { + var debugDescription: String { + return value.debugDescription + } +} diff --git a/SignalUtilitiesKit/Database/Migrations/OWSDatabaseMigrationRunner.m b/SignalUtilitiesKit/Database/Migrations/OWSDatabaseMigrationRunner.m index a7d38515a..91b370f4e 100644 --- a/SignalUtilitiesKit/Database/Migrations/OWSDatabaseMigrationRunner.m +++ b/SignalUtilitiesKit/Database/Migrations/OWSDatabaseMigrationRunner.m @@ -26,6 +26,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSArray *)allMigrations { return @[ + [SNOpenGroupServerIdLookupMigration new], [SNMessageRequestsMigration new], [SNContactsMigration new] ]; diff --git a/SignalUtilitiesKit/Database/Migrations/OpenGroupServerIdLookupMigration.swift b/SignalUtilitiesKit/Database/Migrations/OpenGroupServerIdLookupMigration.swift new file mode 100644 index 000000000..a98a0da07 --- /dev/null +++ b/SignalUtilitiesKit/Database/Migrations/OpenGroupServerIdLookupMigration.swift @@ -0,0 +1,48 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +@objc(SNOpenGroupServerIdLookupMigration) +public class OpenGroupServerIdLookupMigration: OWSDatabaseMigration { + @objc + class func migrationId() -> String { + return "003" + } + + override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) { + self.doMigrationAsync(completion: completion) + } + + private func doMigrationAsync(completion: @escaping OWSDatabaseMigrationCompletion) { + var lookups: [OpenGroupServerIdLookup] = [] + + Storage.write(with: { transaction in + TSGroupThread.enumerateCollectionObjects(with: transaction) { object, _ in + guard let thread: TSGroupThread = object as? TSGroupThread else { return } + guard let threadId: String = thread.uniqueId else { return } + guard let openGroup: OpenGroupV2 = Storage.shared.getV2OpenGroup(for: threadId) else { return } + + thread.enumerateInteractions(with: transaction) { interaction, _ in + guard let tsMessage: TSMessage = interaction as? TSMessage else { return } + guard let tsMessageId: String = tsMessage.uniqueId else { return } + + lookups.append( + OpenGroupServerIdLookup( + server: openGroup.server, + room: openGroup.room, + serverId: tsMessage.openGroupServerMessageID, + tsMessageId: tsMessageId + ) + ) + } + } + + lookups.forEach { lookup in + Storage.shared.addOpenGroupServerIdLookup(lookup, using: transaction) + } + self.save(with: transaction) // Intentionally capture self + }, completion: { + completion() + }) + } +} diff --git a/SignalUtilitiesKit/Messaging/OWSMessageUtils.h b/SignalUtilitiesKit/Messaging/OWSMessageUtils.h index 3069360bf..ef693b8dc 100644 --- a/SignalUtilitiesKit/Messaging/OWSMessageUtils.h +++ b/SignalUtilitiesKit/Messaging/OWSMessageUtils.h @@ -16,7 +16,6 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)sharedManager; - (NSUInteger)unreadMessagesCount; -- (NSUInteger)unreadMessageRequestCount; - (NSUInteger)unreadMessagesCountExcept:(TSThread *)thread; - (void)updateApplicationBadgeCount; diff --git a/SignalUtilitiesKit/Messaging/OWSMessageUtils.m b/SignalUtilitiesKit/Messaging/OWSMessageUtils.m index cbc703c91..543971dbc 100644 --- a/SignalUtilitiesKit/Messaging/OWSMessageUtils.m +++ b/SignalUtilitiesKit/Messaging/OWSMessageUtils.m @@ -93,27 +93,6 @@ NS_ASSUME_NONNULL_BEGIN return count; } -- (NSUInteger)unreadMessageRequestCount { - __block NSUInteger count = 0; - - [LKStorage readWithBlock:^(YapDatabaseReadTransaction *transaction) { - YapDatabaseViewTransaction *unreadMessages = [transaction ext:TSUnreadDatabaseViewExtensionName]; - NSArray *allGroups = [unreadMessages allGroups]; - // FIXME: Confusingly, `allGroups` includes contact threads as well - for (NSString *groupID in allGroups) { - TSThread *thread = [TSThread fetchObjectWithUniqueID:groupID transaction:transaction]; - - // Only increase the count for message requests - if (![thread isMessageRequestUsingTransaction:transaction]) { continue; } - if ([unreadMessages numberOfItemsInGroup:groupID] > 0) { - count += 1; - } - } - }]; - - return count; -} - - (NSUInteger)unreadMessagesCountExcept:(TSThread *)thread { __block NSUInteger numberOfItems;