From aba4cda14379381f1f1a88723ca9b0ecd0cfca67 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Thu, 12 Nov 2020 16:23:34 +1100 Subject: [PATCH] WIP --- .../Jobs/MessageReceiveJob.swift | 8 +- .../Sending & Receiving/MessageReceiver.swift | 2 +- Signal.xcodeproj/project.pbxproj | 8 - SignalUtilitiesKit/FullTextSearchFinder.swift | 2 +- SignalUtilitiesKit/Meta/SignalUtilitiesKit.h | 1 - SignalUtilitiesKit/PublicChatPoller.swift | 5 +- .../SharingThreadPickerViewController.m | 2 - SignalUtilitiesKit/SignalAttachment.swift | 18 +- ...SInvalidIdentityKeyReceivingErrorMessage.m | 3 - SignalUtilitiesKit/TSOutgoingMessage.m | 1 - SignalUtilitiesKit/TSThread.h | 1 - SignalUtilitiesKit/TSThread.m | 5 - SignalUtilitiesKit/ThreadUtil.h | 144 ---- SignalUtilitiesKit/ThreadUtil.m | 797 ------------------ 14 files changed, 21 insertions(+), 976 deletions(-) delete mode 100644 SignalUtilitiesKit/ThreadUtil.h delete mode 100644 SignalUtilitiesKit/ThreadUtil.m diff --git a/SessionMessagingKit/Jobs/MessageReceiveJob.swift b/SessionMessagingKit/Jobs/MessageReceiveJob.swift index 9b95f97ea..1c6f9503a 100644 --- a/SessionMessagingKit/Jobs/MessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/MessageReceiveJob.swift @@ -3,25 +3,29 @@ import SessionUtilitiesKit public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility public var delegate: JobDelegate? private let data: Data + private let messageServerID: UInt64? public var failureCount: UInt = 0 // MARK: Settings public static let maxFailureCount: UInt = 10 // MARK: Initialization - public init(data: Data) { + public init(data: Data, messageServerID: UInt64? = nil) { self.data = data + self.messageServerID = messageServerID } // MARK: Coding public init?(coder: NSCoder) { guard let data = coder.decodeObject(forKey: "data") as! Data? else { return nil } self.data = data + self.messageServerID = coder.decodeObject(forKey: "messageServerUD") as! UInt64? self.failureCount = coder.decodeObject(forKey: "failureCount") as! UInt? ?? 0 } public func encode(with coder: NSCoder) { coder.encode(data, forKey: "data") + coder.encode(messageServerID, forKey: "messageServerID") coder.encode(failureCount, forKey: "failureCount") } @@ -30,7 +34,7 @@ public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NS Configuration.shared.storage.with { transaction in // Intentionally capture self Threading.workQueue.async { do { - let _ = try MessageReceiver.parse(self.data, using: transaction) + let _ = try MessageReceiver.parse(self.data, messageServerID: self.messageServerID, using: transaction) self.handleSuccess() } catch { SNLog("Couldn't parse message due to error: \(error).") diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 759136062..a874a701f 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -30,7 +30,7 @@ internal enum MessageReceiver { } } - internal static func parse(_ data: Data, using transaction: Any) throws -> Message { + internal static func parse(_ data: Data, messageServerID: UInt64?, using transaction: Any) throws -> Message { // Parse the envelope let envelope = try MessageWrapper.unwrap(data: data) // Decrypt the contents diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 266471fa2..5a89d3c8c 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -880,7 +880,6 @@ C38EF31B255B6DBF007E1867 /* OWSPreferences.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF2F1255B6DBB007E1867 /* OWSPreferences.h */; settings = {ATTRIBUTES = (Public, ); }; }; C38EF31C255B6DBF007E1867 /* Searcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F2255B6DBC007E1867 /* Searcher.swift */; }; C38EF31D255B6DBF007E1867 /* UIImage+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F3255B6DBC007E1867 /* UIImage+OWS.swift */; }; - C38EF31E255B6DBF007E1867 /* ThreadUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F4255B6DBC007E1867 /* ThreadUtil.m */; }; C38EF320255B6DBF007E1867 /* OWSScrubbingLogFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F6255B6DBC007E1867 /* OWSScrubbingLogFormatter.m */; }; C38EF321255B6DBF007E1867 /* OWSAudioPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F7255B6DBC007E1867 /* OWSAudioPlayer.m */; }; C38EF322255B6DBF007E1867 /* DebugLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF2F8255B6DBC007E1867 /* DebugLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -890,7 +889,6 @@ C38EF326255B6DBF007E1867 /* ConversationStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2FC255B6DBD007E1867 /* ConversationStyle.swift */; }; C38EF327255B6DBF007E1867 /* BlockListUIUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2FD255B6DBD007E1867 /* BlockListUIUtils.m */; }; C38EF328255B6DBF007E1867 /* OWSContactAvatarBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF2FE255B6DBD007E1867 /* OWSContactAvatarBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C38EF329255B6DBF007E1867 /* ThreadUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF2FF255B6DBD007E1867 /* ThreadUtil.h */; settings = {ATTRIBUTES = (Public, ); }; }; C38EF32A255B6DBF007E1867 /* UIUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF300255B6DBD007E1867 /* UIUtil.m */; }; C38EF32B255B6DBF007E1867 /* OWSFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF301255B6DBD007E1867 /* OWSFormat.h */; settings = {ATTRIBUTES = (Public, ); }; }; C38EF32D255B6DBF007E1867 /* BlockListUIUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF303255B6DBE007E1867 /* BlockListUIUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -2225,7 +2223,6 @@ C38EF2F1255B6DBB007E1867 /* OWSPreferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSPreferences.h; path = SignalUtilitiesKit/OWSPreferences.h; sourceTree = SOURCE_ROOT; }; C38EF2F2255B6DBC007E1867 /* Searcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Searcher.swift; path = SignalUtilitiesKit/Searcher.swift; sourceTree = SOURCE_ROOT; }; C38EF2F3255B6DBC007E1867 /* UIImage+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIImage+OWS.swift"; path = "SignalUtilitiesKit/UIImage+OWS.swift"; sourceTree = SOURCE_ROOT; }; - C38EF2F4255B6DBC007E1867 /* ThreadUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ThreadUtil.m; path = SignalUtilitiesKit/ThreadUtil.m; sourceTree = SOURCE_ROOT; }; C38EF2F5255B6DBC007E1867 /* OWSAudioPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSAudioPlayer.h; path = SignalUtilitiesKit/OWSAudioPlayer.h; sourceTree = SOURCE_ROOT; }; C38EF2F6255B6DBC007E1867 /* OWSScrubbingLogFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSScrubbingLogFormatter.m; path = SignalUtilitiesKit/OWSScrubbingLogFormatter.m; sourceTree = SOURCE_ROOT; }; C38EF2F7255B6DBC007E1867 /* OWSAudioPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSAudioPlayer.m; path = SignalUtilitiesKit/OWSAudioPlayer.m; sourceTree = SOURCE_ROOT; }; @@ -2236,7 +2233,6 @@ C38EF2FC255B6DBD007E1867 /* ConversationStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConversationStyle.swift; path = SignalUtilitiesKit/ConversationStyle.swift; sourceTree = SOURCE_ROOT; }; C38EF2FD255B6DBD007E1867 /* BlockListUIUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BlockListUIUtils.m; path = SignalUtilitiesKit/BlockListUIUtils.m; sourceTree = SOURCE_ROOT; }; C38EF2FE255B6DBD007E1867 /* OWSContactAvatarBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSContactAvatarBuilder.h; path = SignalUtilitiesKit/OWSContactAvatarBuilder.h; sourceTree = SOURCE_ROOT; }; - C38EF2FF255B6DBD007E1867 /* ThreadUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ThreadUtil.h; path = SignalUtilitiesKit/ThreadUtil.h; sourceTree = SOURCE_ROOT; }; C38EF300255B6DBD007E1867 /* UIUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = UIUtil.m; path = SignalUtilitiesKit/UIUtil.m; sourceTree = SOURCE_ROOT; }; C38EF301255B6DBD007E1867 /* OWSFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSFormat.h; path = SignalUtilitiesKit/OWSFormat.h; sourceTree = SOURCE_ROOT; }; C38EF302255B6DBE007E1867 /* OWSAnyTouchGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSAnyTouchGestureRecognizer.h; path = SignalUtilitiesKit/OWSAnyTouchGestureRecognizer.h; sourceTree = SOURCE_ROOT; }; @@ -3468,8 +3464,6 @@ C38EF306255B6DBE007E1867 /* OWSWindowManager.m */, C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */, C38EF2F2255B6DBC007E1867 /* Searcher.swift */, - C38EF2FF255B6DBD007E1867 /* ThreadUtil.h */, - C38EF2F4255B6DBC007E1867 /* ThreadUtil.m */, C38EF307255B6DBE007E1867 /* UIGestureRecognizer+OWS.swift */, C38EF2F3255B6DBC007E1867 /* UIImage+OWS.swift */, C38EF30A255B6DBE007E1867 /* UIUtil.h */, @@ -4562,7 +4556,6 @@ C33FDC6F255A582000E217F9 /* TSNetworkManager.h in Headers */, C33FDD4B255A582000E217F9 /* ProtoUtils.h in Headers */, C33FDD19255A582000E217F9 /* YapDatabaseConnection+OWS.h in Headers */, - C38EF329255B6DBF007E1867 /* ThreadUtil.h in Headers */, C33FDD1F255A582000E217F9 /* OWSSyncConfigurationMessage.h in Headers */, C38EF216255B6D3B007E1867 /* Theme.h in Headers */, C38EF31B255B6DBF007E1867 /* OWSPreferences.h in Headers */, @@ -5827,7 +5820,6 @@ C33FDC26255A581F00E217F9 /* ProtoUtils.m in Sources */, C33FDC48255A581F00E217F9 /* OWSFileSystem.m in Sources */, C33FDC5E255A582000E217F9 /* SSKProto.swift in Sources */, - C38EF31E255B6DBF007E1867 /* ThreadUtil.m in Sources */, C38EF39E255B6DDA007E1867 /* OWSQuotedReplyModel.m in Sources */, C33FDD85255A582000E217F9 /* TSInvalidIdentityKeyReceivingErrorMessage.m in Sources */, C33FDD2E255A582000E217F9 /* TSInvalidIdentityKeyErrorMessage.m in Sources */, diff --git a/SignalUtilitiesKit/FullTextSearchFinder.swift b/SignalUtilitiesKit/FullTextSearchFinder.swift index 5ead04baf..1fcd44e96 100644 --- a/SignalUtilitiesKit/FullTextSearchFinder.swift +++ b/SignalUtilitiesKit/FullTextSearchFinder.swift @@ -216,7 +216,7 @@ public class FullTextSearchFinder: NSObject { if let groupThread = object as? TSGroupThread { return self.groupThreadIndexer.index(groupThread, transaction: transaction) } else if let contactThread = object as? TSContactThread { - guard contactThread.shouldThreadBeVisible && !contactThread.isSlaveThread else { + guard contactThread.shouldThreadBeVisible else { // If we've never sent/received a message in a TSContactThread, // then we want it to appear in the "Other Contacts" section rather // than in the "Conversations" section. diff --git a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h index 309c405a0..18c71fc06 100644 --- a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h +++ b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h @@ -102,7 +102,6 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[]; #import #import #import -#import #import #import #import diff --git a/SignalUtilitiesKit/PublicChatPoller.swift b/SignalUtilitiesKit/PublicChatPoller.swift index dfd03a974..9d1dbf10c 100644 --- a/SignalUtilitiesKit/PublicChatPoller.swift +++ b/SignalUtilitiesKit/PublicChatPoller.swift @@ -178,7 +178,10 @@ public final class PublicChatPoller : NSObject { Storage.writeSync { transaction in transaction.setObject(senderDisplayName, forKey: senderPublicKey, inCollection: publicChat.id) let messageServerID = message.serverID - SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction, serverID: messageServerID ?? 0) + let job = MessageReceiveJob(data: try! envelope.buildSerializedData(), messageServerID: messageServerID) + Storage.write { transaction in + SessionMessagingKit.JobQueue.shared.add(job, using: transaction) + } // If we got a message from our master device then we should use its profile picture if let profilePicture = message.profilePicture, masterPublicKey == message.senderPublicKey { if (message.displayName.count > 0) { diff --git a/SignalUtilitiesKit/SharingThreadPickerViewController.m b/SignalUtilitiesKit/SharingThreadPickerViewController.m index 5f8fcc8c0..9a85d0f96 100644 --- a/SignalUtilitiesKit/SharingThreadPickerViewController.m +++ b/SignalUtilitiesKit/SharingThreadPickerViewController.m @@ -4,14 +4,12 @@ #import "SharingThreadPickerViewController.h" #import "SignalApp.h" -#import "ThreadUtil.h" #import "UIColor+OWS.h" #import "UIFont+OWS.h" #import "UIView+OWS.h" #import #import #import -#import #import #import diff --git a/SignalUtilitiesKit/SignalAttachment.swift b/SignalUtilitiesKit/SignalAttachment.swift index 42f48edd3..0a9aa7385 100644 --- a/SignalUtilitiesKit/SignalAttachment.swift +++ b/SignalUtilitiesKit/SignalAttachment.swift @@ -244,15 +244,15 @@ public class SignalAttachment: NSObject { return errorDescription } - @objc - public func buildOutgoingAttachmentInfo(message: TSMessage) -> OutgoingAttachmentInfo { - assert(message.uniqueId != nil) - return OutgoingAttachmentInfo(dataSource: dataSource, - contentType: mimeType, - sourceFilename: filenameOrDefault, - caption: captionText, - albumMessageId: message.uniqueId) - } +// @objc +// public func buildOutgoingAttachmentInfo(message: TSMessage) -> OutgoingAttachmentInfo { +// assert(message.uniqueId != nil) +// return OutgoingAttachmentInfo(dataSource: dataSource, +// contentType: mimeType, +// sourceFilename: filenameOrDefault, +// caption: captionText, +// albumMessageId: message.uniqueId) +// } @objc public func staticThumbnail() -> UIImage? { diff --git a/SignalUtilitiesKit/TSInvalidIdentityKeyReceivingErrorMessage.m b/SignalUtilitiesKit/TSInvalidIdentityKeyReceivingErrorMessage.m index caf7ccbdb..034853307 100644 --- a/SignalUtilitiesKit/TSInvalidIdentityKeyReceivingErrorMessage.m +++ b/SignalUtilitiesKit/TSInvalidIdentityKeyReceivingErrorMessage.m @@ -5,8 +5,6 @@ #import "TSInvalidIdentityKeyReceivingErrorMessage.h" #import "OWSFingerprint.h" #import "OWSIdentityManager.h" -#import "OWSMessageManager.h" -#import "OWSMessageReceiver.h" #import "OWSPrimaryStorage+SessionStore.h" #import "OWSPrimaryStorage.h" #import "SSKEnvironment.h" @@ -108,7 +106,6 @@ __attribute__((deprecated)) @interface TSInvalidIdentityKeyReceivingErrorMessage [self.thread receivedMessagesForInvalidKey:newKey]; for (TSInvalidIdentityKeyReceivingErrorMessage *errorMessage in messagesToDecrypt) { - [SSKEnvironment.shared.messageReceiver handleReceivedEnvelopeData:errorMessage.envelopeData]; // Here we remove the existing error message because handleReceivedEnvelope will either // 1.) succeed and create a new successful message in the thread or... diff --git a/SignalUtilitiesKit/TSOutgoingMessage.m b/SignalUtilitiesKit/TSOutgoingMessage.m index b30739593..f681ccb6b 100644 --- a/SignalUtilitiesKit/TSOutgoingMessage.m +++ b/SignalUtilitiesKit/TSOutgoingMessage.m @@ -7,7 +7,6 @@ #import "TSOutgoingMessage.h" #import "NSString+SSK.h" #import "OWSContact.h" -#import "OWSMessageSender.h" #import "OWSOutgoingSyncMessage.h" #import "OWSPrimaryStorage.h" #import "ProfileManagerProtocol.h" diff --git a/SignalUtilitiesKit/TSThread.h b/SignalUtilitiesKit/TSThread.h index 6eaef5ca4..bcf077447 100644 --- a/SignalUtilitiesKit/TSThread.h +++ b/SignalUtilitiesKit/TSThread.h @@ -38,7 +38,6 @@ extern ConversationColorName const kConversationColorName_Default; @property (nonatomic, readonly) NSDate *creationDate; @property (nonatomic, readonly) BOOL isArchivedByLegacyTimestampForSorting; @property (nonatomic, readonly) TSInteraction *lastInteraction; -@property (nonatomic, readonly) BOOL isSlaveThread; /** * Whether the object is a group thread or not. diff --git a/SignalUtilitiesKit/TSThread.m b/SignalUtilitiesKit/TSThread.m index 3ef2ecaf7..773c00a1c 100644 --- a/SignalUtilitiesKit/TSThread.m +++ b/SignalUtilitiesKit/TSThread.m @@ -730,11 +730,6 @@ ConversationColorName const kConversationColorName_Default = ConversationColorNa }]; } -- (BOOL)isSlaveThread -{ - return [LKMultiDeviceProtocol isSlaveThread:self]; -} - @end NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/ThreadUtil.h b/SignalUtilitiesKit/ThreadUtil.h deleted file mode 100644 index ec5070d1f..000000000 --- a/SignalUtilitiesKit/ThreadUtil.h +++ /dev/null @@ -1,144 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class LKDeviceLinkMessage; -@class LKUnlinkDeviceMessage; -@class OWSBlockingManager; -@class OWSContact; -@class OWSContactsManager; -@class OWSLinkPreviewDraft; -@class OWSMessageSender; -@class OWSQuotedReplyModel; -@class OWSUnreadIndicator; -@class SignalAttachment; -@class TSContactThread; -@class TSGroupThread; -@class TSInteraction; -@class TSOutgoingMessage; -@class TSThread; -@class YapDatabaseConnection; -@class YapDatabaseReadTransaction; -@class YapDatabaseReadWriteTransaction; - -@interface ThreadDynamicInteractions : NSObject - -// Represents the "reverse index" of the focus message, if any. -// The "reverse index" is the distance of this interaction from -// the last interaction in the thread. Therefore the last interaction -// will have a "reverse index" of zero. -// -// We use "reverse indices" because (among other uses) we use this to -// determine the initial load window size. -@property (nonatomic, nullable, readonly) NSNumber *focusMessagePosition; - -@property (nonatomic, nullable, readonly) OWSUnreadIndicator *unreadIndicator; - -- (void)clearUnreadIndicatorState; - -@end - -#pragma mark - - -@interface ThreadUtil : NSObject - -#pragma mark - Durable Message Enqueue - -+ (void)enqueueDeviceLinkMessage:(LKDeviceLinkMessage *)message; - -+ (TSOutgoingMessage *)enqueueMessageWithText:(NSString *)fullMessageText - inThread:(TSThread *)thread - quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel - linkPreviewDraft:(nullable nullable OWSLinkPreviewDraft *)linkPreviewDraft - transaction:(YapDatabaseReadTransaction *)transaction; - -+ (TSOutgoingMessage *)enqueueMessageWithText:(nullable NSString *)fullMessageText - mediaAttachments:(NSArray *)attachments - inThread:(TSThread *)thread - quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel - linkPreviewDraft:(nullable nullable OWSLinkPreviewDraft *)linkPreviewDraft - transaction:(YapDatabaseReadTransaction *)transaction; - -+ (TSOutgoingMessage *)enqueueMessageWithContactShare:(OWSContact *)contactShare inThread:(TSThread *)thread; -+ (void)enqueueLeaveGroupMessageInThread:(TSGroupThread *)thread; - -#pragma mark - Non-Durable Sending - -// Used by SAE and "reply from lockscreen", otherwise we should use the durable `enqueue` counterpart -+ (TSOutgoingMessage *)sendMessageNonDurablyWithText:(NSString *)fullMessageText - inThread:(TSThread *)thread - quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel - transaction:(YapDatabaseReadTransaction *)transaction - messageSender:(OWSMessageSender *)messageSender - completion:(void (^)(NSError *_Nullable error))completion; - -// Used by SAE, otherwise we should use the durable `enqueue` counterpart -+ (TSOutgoingMessage *)sendMessageNonDurablyWithText:(NSString *)fullMessageText - mediaAttachments:(NSArray *)attachments - inThread:(TSThread *)thread - quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel - transaction:(YapDatabaseReadTransaction *)transaction - messageSender:(OWSMessageSender *)messageSender - completion:(void (^)(NSError *_Nullable error))completion; - -// Used by SAE, otherwise we should use the durable `enqueue` counterpart -+ (TSOutgoingMessage *)sendMessageNonDurablyWithContactShare:(OWSContact *)contactShare - inThread:(TSThread *)thread - messageSender:(OWSMessageSender *)messageSender - completion:(void (^)(NSError *_Nullable error))completion; - - -#pragma mark - dynamic interactions - -// This method will create and/or remove any offers and indicators -// necessary for this thread. This includes: -// -// * Block offers. -// * "Add to contacts" offers. -// * Unread indicators. -// -// Parameters: -// -// * hideUnreadMessagesIndicator: If YES, the "unread indicator" has -// been cleared and should not be shown. -// * firstUnseenInteractionTimestamp: A snapshot of unseen message state -// when we entered the conversation view. See comments on -// ThreadOffersAndIndicators. -// * maxRangeSize: Loading a lot of messages in conversation view is -// slow and unwieldy. This number represents the maximum current -// size of the "load window" in that view. The unread indicator should -// always be inserted within that window. -+ (ThreadDynamicInteractions *)ensureDynamicInteractionsForThread:(TSThread *)thread - contactsManager:(OWSContactsManager *)contactsManager - blockingManager:(OWSBlockingManager *)blockingManager - dbConnection:(YapDatabaseConnection *)dbConnection - hideUnreadMessagesIndicator:(BOOL)hideUnreadMessagesIndicator - lastUnreadIndicator:(nullable OWSUnreadIndicator *)lastUnreadIndicator - focusMessageId:(nullable NSString *)focusMessageId - maxRangeSize:(int)maxRangeSize; - -+ (BOOL)shouldShowGroupProfileBannerInThread:(TSThread *)thread blockingManager:(OWSBlockingManager *)blockingManager; - -// This method should be called right _before_ we send a message to a thread, -// since we want to auto-add contact threads to the profile whitelist if the -// conversation was initiated by the local user. -// -// Returns YES IFF the thread was just added to the profile whitelist. -+ (BOOL)addThreadToProfileWhitelistIfEmptyContactThread:(TSThread *)thread; - -#pragma mark - Delete Content - -+ (void)deleteAllContent; - -#pragma mark - Find Content - -+ (nullable TSInteraction *)findInteractionInThreadByTimestamp:(uint64_t)timestamp - authorId:(NSString *)authorId - threadUniqueId:(NSString *)threadUniqueId - transaction:(YapDatabaseReadTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/ThreadUtil.m b/SignalUtilitiesKit/ThreadUtil.m deleted file mode 100644 index 37b57b6cd..000000000 --- a/SignalUtilitiesKit/ThreadUtil.m +++ /dev/null @@ -1,797 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "ThreadUtil.h" -#import "OWSContactOffersInteraction.h" -#import "OWSContactsManager.h" -#import "OWSQuotedReplyModel.h" -#import "OWSUnreadIndicator.h" -#import "TSUnreadIndicatorInteraction.h" -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - - -NS_ASSUME_NONNULL_BEGIN - -@interface ThreadDynamicInteractions () - -@property (nonatomic, nullable) NSNumber *focusMessagePosition; - -@property (nonatomic, nullable) OWSUnreadIndicator *unreadIndicator; - -@end - -#pragma mark - - -@implementation ThreadDynamicInteractions - -- (void)clearUnreadIndicatorState -{ - self.unreadIndicator = nil; -} - -- (BOOL)isEqual:(id)object -{ - if (self == object) { - return YES; - } - - if (![object isKindOfClass:[ThreadDynamicInteractions class]]) { - return NO; - } - - ThreadDynamicInteractions *other = (ThreadDynamicInteractions *)object; - return ([NSObject isNullableObject:self.focusMessagePosition equalTo:other.focusMessagePosition] && - [NSObject isNullableObject:self.unreadIndicator equalTo:other.unreadIndicator]); -} - -@end - -#pragma mark - - -typedef void (^BuildOutgoingMessageCompletionBlock)(TSOutgoingMessage *savedMessage, - NSMutableArray *attachmentInfos, - YapDatabaseReadWriteTransaction *writeTransaction); - -@implementation ThreadUtil - -#pragma mark - Dependencies - -+ (SSKMessageSenderJobQueue *)messageSenderJobQueue -{ - return SSKEnvironment.shared.messageSenderJobQueue; -} - -+ (YapDatabaseConnection *)dbConnection -{ - return SSKEnvironment.shared.primaryStorage.dbReadWriteConnection; -} - -#pragma mark - Durable Message Enqueue - -+ (void)enqueueDeviceLinkMessage:(LKDeviceLinkMessage *)message -{ - [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [self.messageSenderJobQueue addMessage:message transaction:transaction]; - }]; -} - -+ (TSOutgoingMessage *)enqueueMessageWithText:(NSString *)fullMessageText - inThread:(TSThread *)thread - quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel - linkPreviewDraft:(nullable nullable OWSLinkPreviewDraft *)linkPreviewDraft - transaction:(YapDatabaseReadTransaction *)transaction -{ - return [self enqueueMessageWithText:fullMessageText - mediaAttachments:@[] - inThread:thread - quotedReplyModel:quotedReplyModel - linkPreviewDraft:linkPreviewDraft - transaction:transaction]; -} - -+ (TSOutgoingMessage *)enqueueMessageWithText:(nullable NSString *)fullMessageText - mediaAttachments:(NSArray *)mediaAttachments - inThread:(TSThread *)thread - quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel - linkPreviewDraft:(nullable nullable OWSLinkPreviewDraft *)linkPreviewDraft - transaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(thread); - - return [self - buildOutgoingMessageWithText:fullMessageText - mediaAttachments:mediaAttachments - thread:thread - quotedReplyModel:quotedReplyModel - linkPreviewDraft:linkPreviewDraft - transaction:transaction - completion:^(TSOutgoingMessage *savedMessage, - NSMutableArray *attachmentInfos, - YapDatabaseReadWriteTransaction *writeTransaction) { - if (attachmentInfos.count == 0) { - [self.messageSenderJobQueue addMessage:savedMessage transaction:writeTransaction]; - } else { - [self.messageSenderJobQueue addMediaMessage:savedMessage - attachmentInfos:attachmentInfos - isTemporaryAttachment:NO]; - } - }]; -} - -+ (TSOutgoingMessage *)buildOutgoingMessageWithText:(nullable NSString *)fullMessageText - mediaAttachments:(NSArray *)mediaAttachments - thread:(TSThread *)thread - quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel - linkPreviewDraft:(nullable OWSLinkPreviewDraft *)linkPreviewDraft - transaction:(YapDatabaseReadTransaction *)transaction - completion:(BuildOutgoingMessageCompletionBlock)completionBlock; -{ - NSString *_Nullable truncatedText; - NSArray *attachments = mediaAttachments; - if ([fullMessageText lengthOfBytesUsingEncoding:NSUTF8StringEncoding] <= kOversizeTextMessageSizeThreshold) { - truncatedText = fullMessageText; - } else { - if (SSKFeatureFlags.sendingMediaWithOversizeText) { - truncatedText = [fullMessageText ows_truncatedToByteCount:kOversizeTextMessageSizeThreshold]; - } else { - // Legacy iOS clients already support receiving long text, but they assume _any_ body - // text is the _full_ body text. So until we consider "rollout" complete, we maintain - // the legacy sending behavior, which does not include the truncated text in the - // websocket proto. - truncatedText = nil; - } - DataSource *_Nullable dataSource = [DataSourceValue dataSourceWithOversizeText:fullMessageText]; - if (dataSource) { - SignalAttachment *oversizeTextAttachment = - [SignalAttachment attachmentWithDataSource:dataSource dataUTI:kOversizeTextAttachmentUTI]; - attachments = [mediaAttachments arrayByAddingObject:oversizeTextAttachment]; - } else { - OWSFailDebug(@"dataSource was unexpectedly nil."); - } - } - - OWSDisappearingMessagesConfiguration *configuration = - [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId transaction:transaction]; - - uint32_t expiresInSeconds = (configuration.isEnabled ? configuration.durationSeconds : 0); - - for (SignalAttachment *attachment in attachments) { - OWSAssertDebug(!attachment.hasError); - OWSAssertDebug(attachment.mimeType.length > 0); - } - - BOOL isVoiceMessage = (attachments.count == 1 && attachments.lastObject.isVoiceMessage); - - TSOutgoingMessage *message = - [[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:thread - messageBody:truncatedText - attachmentIds:[NSMutableArray new] - expiresInSeconds:expiresInSeconds - expireStartedAt:0 - isVoiceMessage:isVoiceMessage - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:[quotedReplyModel buildQuotedMessageForSending] - contactShare:nil - linkPreview:nil]; - [message save]; - - [BenchManager - benchAsyncWithTitle:@"Saving outgoing message" - block:^(void (^benchmarkCompletion)(void)) { - // To avoid blocking the send flow, we dispatch an async write from within this read - // transaction - [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull writeTransaction) { - - OWSLinkPreview *_Nullable linkPreview = - [self linkPreviewForLinkPreviewDraft:linkPreviewDraft - transaction:writeTransaction]; - if (linkPreview) { - [message updateWithLinkPreview:linkPreview transaction:writeTransaction]; - } - - NSMutableArray *attachmentInfos = [NSMutableArray new]; - for (SignalAttachment *attachment in attachments) { - OWSOutgoingAttachmentInfo *attachmentInfo = [attachment buildOutgoingAttachmentInfoWithMessage:message]; - [attachmentInfos addObject:attachmentInfo]; - } - completionBlock(message, attachmentInfos, writeTransaction); - } completion:benchmarkCompletion]; - }]; - - return message; -} - -+ (TSOutgoingMessage *)enqueueMessageWithContactShare:(OWSContact *)contactShare inThread:(TSThread *)thread; -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(contactShare); - OWSAssertDebug(contactShare.ows_isValid); - OWSAssertDebug(thread); - - OWSDisappearingMessagesConfiguration *configuration = - [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId]; - - uint32_t expiresInSeconds = (configuration.isEnabled ? configuration.durationSeconds : 0); - TSOutgoingMessage *message = - [[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:thread - messageBody:nil - attachmentIds:[NSMutableArray new] - expiresInSeconds:expiresInSeconds - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:contactShare - linkPreview:nil]; - - [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [message saveWithTransaction:transaction]; - [self.messageSenderJobQueue addMessage:message transaction:transaction]; - }]; - - return message; -} - -+ (void)enqueueLeaveGroupMessageInThread:(TSGroupThread *)thread -{ - OWSAssertDebug([thread isKindOfClass:[TSGroupThread class]]); - - TSOutgoingMessage *message = - [TSOutgoingMessage outgoingMessageInThread:thread groupMetaMessage:TSGroupMetaMessageQuit expiresInSeconds:0]; - - [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [self.messageSenderJobQueue addMessage:message transaction:transaction]; - }]; -} - -// MARK: Non-Durable Sending - -// We might want to generate a link preview here. -+ (TSOutgoingMessage *)sendMessageNonDurablyWithText:(NSString *)fullMessageText - inThread:(TSThread *)thread - quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel - transaction:(YapDatabaseReadTransaction *)transaction - messageSender:(OWSMessageSender *)messageSender - completion:(void (^)(NSError *_Nullable error))completion -{ - OWSAssertDebug(completion); - - return [self sendMessageNonDurablyWithText:fullMessageText - mediaAttachments:@[] - inThread:thread - quotedReplyModel:quotedReplyModel - transaction:transaction - messageSender:messageSender - completion:completion]; -} - -+ (TSOutgoingMessage *)sendMessageNonDurablyWithText:(NSString *)fullMessageText - mediaAttachments:(NSArray *)mediaAttachments - inThread:(TSThread *)thread - quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel - transaction:(YapDatabaseReadTransaction *)transaction - messageSender:(OWSMessageSender *)messageSender - completion:(void (^)(NSError *_Nullable error))completion -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(thread); - OWSAssertDebug(completion); - - return - [self buildOutgoingMessageWithText:fullMessageText - mediaAttachments:mediaAttachments - thread:thread - quotedReplyModel:quotedReplyModel - linkPreviewDraft:nil - transaction:transaction - completion:^(TSOutgoingMessage *_Nonnull savedMessage, - NSMutableArray *_Nonnull attachmentInfos, - YapDatabaseReadWriteTransaction *_Nonnull writeTransaction) { - if (attachmentInfos.count == 0) { - [messageSender sendMessage:savedMessage - success:^{ - dispatch_async(dispatch_get_main_queue(), ^(void) { - completion(nil); - }); - } - failure:^(NSError *error) { - dispatch_async(dispatch_get_main_queue(), ^(void) { - completion(error); - }); - }]; - } else { - [messageSender sendAttachments:attachmentInfos - inMessage:savedMessage - success:^{ - dispatch_async(dispatch_get_main_queue(), ^(void) { - completion(nil); - }); - } - failure:^(NSError *error) { - dispatch_async(dispatch_get_main_queue(), ^(void) { - completion(error); - }); - }]; - } - }]; -} - -+ (TSOutgoingMessage *)sendMessageNonDurablyWithContactShare:(OWSContact *)contactShare - inThread:(TSThread *)thread - messageSender:(OWSMessageSender *)messageSender - completion:(void (^)(NSError *_Nullable error))completion -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(contactShare); - OWSAssertDebug(contactShare.ows_isValid); - OWSAssertDebug(thread); - OWSAssertDebug(messageSender); - OWSAssertDebug(completion); - - OWSDisappearingMessagesConfiguration *configuration = - [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId]; - - uint32_t expiresInSeconds = (configuration.isEnabled ? configuration.durationSeconds : 0); - // MJK TODO - remove senderTimestamp - TSOutgoingMessage *message = - [[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:thread - messageBody:nil - attachmentIds:[NSMutableArray new] - expiresInSeconds:expiresInSeconds - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:contactShare - linkPreview:nil]; - - [messageSender sendMessage:message - success:^{ - OWSLogDebug(@"Successfully sent contact share."); - dispatch_async(dispatch_get_main_queue(), ^(void) { - completion(nil); - }); - } - failure:^(NSError *error) { - OWSLogError(@"Failed to send contact share with error: %@", error); - dispatch_async(dispatch_get_main_queue(), ^(void) { - completion(error); - }); - }]; - - return message; -} - -+ (nullable OWSLinkPreview *)linkPreviewForLinkPreviewDraft:(nullable OWSLinkPreviewDraft *)linkPreviewDraft - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - if (!linkPreviewDraft) { - return nil; - } - NSError *linkPreviewError; - OWSLinkPreview *_Nullable linkPreview = [OWSLinkPreview buildValidatedLinkPreviewFromInfo:linkPreviewDraft - transaction:transaction - error:&linkPreviewError]; - if (linkPreviewError && ![OWSLinkPreview isNoPreviewError:linkPreviewError]) { - OWSLogError(@"linkPreviewError: %@", linkPreviewError); - } - return linkPreview; -} - -#pragma mark - Dynamic Interactions - -+ (ThreadDynamicInteractions *)ensureDynamicInteractionsForThread:(TSThread *)thread - contactsManager:(OWSContactsManager *)contactsManager - blockingManager:(OWSBlockingManager *)blockingManager - dbConnection:(YapDatabaseConnection *)dbConnection - hideUnreadMessagesIndicator:(BOOL)hideUnreadMessagesIndicator - lastUnreadIndicator:(nullable OWSUnreadIndicator *)lastUnreadIndicator - focusMessageId:(nullable NSString *)focusMessageId - maxRangeSize:(int)maxRangeSize -{ - OWSAssertDebug(thread); - OWSAssertDebug(dbConnection); - OWSAssertDebug(contactsManager); - OWSAssertDebug(blockingManager); - OWSAssertDebug(maxRangeSize > 0); - - ThreadDynamicInteractions *result = [ThreadDynamicInteractions new]; - - [dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - // Find any "dynamic" interactions and safety number changes. - // - // We use different views for performance reasons. - NSMutableArray *blockingSafetyNumberChanges = [NSMutableArray new]; - NSMutableArray *nonBlockingSafetyNumberChanges = [NSMutableArray new]; - [[TSDatabaseView threadSpecialMessagesDatabaseView:transaction] - enumerateKeysAndObjectsInGroup:thread.uniqueId - usingBlock:^( - NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { - if ([object isKindOfClass:[TSInvalidIdentityKeyErrorMessage class]]) { - [blockingSafetyNumberChanges addObject:object]; - } else if ([object isKindOfClass:[TSErrorMessage class]]) { - TSErrorMessage *errorMessage = (TSErrorMessage *)object; - OWSAssertDebug( - errorMessage.errorType == TSErrorMessageNonBlockingIdentityChange); - [nonBlockingSafetyNumberChanges addObject:errorMessage]; - } else { - OWSFailDebug(@"Unexpected dynamic interaction type: %@", [object class]); - } - }]; - - // Determine if there are "unread" messages in this conversation. - // If we've been passed a firstUnseenInteractionTimestampParameter, - // just use that value in order to preserve continuity of the - // unread messages indicator after all messages in the conversation - // have been marked as read. - // - // IFF this variable is non-null, there are unseen messages in the thread. - NSNumber *_Nullable firstUnseenSortId = nil; - if (lastUnreadIndicator) { - firstUnseenSortId = @(lastUnreadIndicator.firstUnseenSortId); - } else { - TSInteraction *_Nullable firstUnseenInteraction = - [[TSDatabaseView unseenDatabaseViewExtension:transaction] firstObjectInGroup:thread.uniqueId]; - if (firstUnseenInteraction && firstUnseenInteraction.sortId != NULL) { - firstUnseenSortId = @(firstUnseenInteraction.sortId); - } - } - - [self ensureUnreadIndicator:result - thread:thread - transaction:transaction - maxRangeSize:maxRangeSize - blockingSafetyNumberChanges:blockingSafetyNumberChanges - nonBlockingSafetyNumberChanges:nonBlockingSafetyNumberChanges - hideUnreadMessagesIndicator:hideUnreadMessagesIndicator - firstUnseenSortId:firstUnseenSortId]; - - // Determine the position of the focus message _after_ performing any mutations - // around dynamic interactions. - if (focusMessageId != nil) { - result.focusMessagePosition = - [self focusMessagePositionForThread:thread transaction:transaction focusMessageId:focusMessageId]; - } - }]; - - return result; -} - -+ (void)ensureUnreadIndicator:(ThreadDynamicInteractions *)dynamicInteractions - thread:(TSThread *)thread - transaction:(YapDatabaseReadTransaction *)transaction - maxRangeSize:(int)maxRangeSize - blockingSafetyNumberChanges:(NSArray *)blockingSafetyNumberChanges - nonBlockingSafetyNumberChanges:(NSArray *)nonBlockingSafetyNumberChanges - hideUnreadMessagesIndicator:(BOOL)hideUnreadMessagesIndicator - firstUnseenSortId:(nullable NSNumber *)firstUnseenSortId -{ - OWSAssertDebug(dynamicInteractions); - OWSAssertDebug(thread); - OWSAssertDebug(transaction); - OWSAssertDebug(blockingSafetyNumberChanges); - OWSAssertDebug(nonBlockingSafetyNumberChanges); - - if (hideUnreadMessagesIndicator) { - return; - } - if (!firstUnseenSortId) { - // If there are no unseen interactions, don't show an unread indicator. - return; - } - - YapDatabaseViewTransaction *threadMessagesTransaction = [transaction ext:TSMessageDatabaseViewExtensionName]; - OWSAssertDebug([threadMessagesTransaction isKindOfClass:[YapDatabaseViewTransaction class]]); - - // Determine unread indicator position, if necessary. - // - // Enumerate in reverse to count the number of messages - // after the unseen messages indicator. Not all of - // them are unnecessarily unread, but we need to tell - // the messages view the position of the unread indicator, - // so that it can widen its "load window" to always show - // the unread indicator. - __block long visibleUnseenMessageCount = 0; - __block TSInteraction *interactionAfterUnreadIndicator = nil; - __block BOOL hasMoreUnseenMessages = NO; - [threadMessagesTransaction - enumerateKeysAndObjectsInGroup:thread.uniqueId - withOptions:NSEnumerationReverse - usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { - if (![object isKindOfClass:[TSInteraction class]]) { - OWSFailDebug(@"Expected a TSInteraction: %@", [object class]); - return; - } - - TSInteraction *interaction = (TSInteraction *)object; - - if (interaction.isDynamicInteraction) { - // Ignore dynamic interactions, if any. - return; - } - - if (interaction.sortId < firstUnseenSortId.unsignedLongLongValue) { - // By default we want the unread indicator to appear just before - // the first unread message. - *stop = YES; - return; - } - - visibleUnseenMessageCount++; - - interactionAfterUnreadIndicator = interaction; - - if (visibleUnseenMessageCount + 1 >= maxRangeSize) { - // If there are more unseen messages than can be displayed in the - // messages view, show the unread indicator at the top of the - // displayed messages. - *stop = YES; - hasMoreUnseenMessages = YES; - } - }]; - - if (!interactionAfterUnreadIndicator) { - // If we can't find an interaction after the unread indicator, - // don't show it. All unread messages may have been deleted or - // expired. - return; - } - OWSAssertDebug(visibleUnseenMessageCount > 0); - - NSUInteger missingUnseenSafetyNumberChangeCount = 0; - if (hasMoreUnseenMessages) { - NSMutableSet *missingUnseenSafetyNumberChanges = [NSMutableSet set]; - for (TSInvalidIdentityKeyErrorMessage *safetyNumberChange in blockingSafetyNumberChanges) { - BOOL isUnseen = safetyNumberChange.sortId >= firstUnseenSortId.unsignedLongLongValue; - if (!isUnseen) { - continue; - } - - BOOL isMissing = safetyNumberChange.sortId < interactionAfterUnreadIndicator.sortId; - if (!isMissing) { - continue; - } - - @try { - NSData *_Nullable newIdentityKey = [safetyNumberChange throws_newIdentityKey]; - if (newIdentityKey == nil) { - OWSFailDebug(@"Safety number change was missing it's new identity key."); - continue; - } - - [missingUnseenSafetyNumberChanges addObject:newIdentityKey]; - } @catch (NSException *exception) { - OWSFailDebug(@"exception: %@", exception); - } - } - - // Count the de-duplicated "blocking" safety number changes and all - // of the "non-blocking" safety number changes. - missingUnseenSafetyNumberChangeCount - = (missingUnseenSafetyNumberChanges.count + nonBlockingSafetyNumberChanges.count); - } - - NSInteger unreadIndicatorPosition = visibleUnseenMessageCount; - - dynamicInteractions.unreadIndicator = - [[OWSUnreadIndicator alloc] initWithFirstUnseenSortId:firstUnseenSortId.unsignedLongLongValue - hasMoreUnseenMessages:hasMoreUnseenMessages - missingUnseenSafetyNumberChangeCount:missingUnseenSafetyNumberChangeCount - unreadIndicatorPosition:unreadIndicatorPosition]; - OWSLogInfo(@"Creating Unread Indicator: %llu", dynamicInteractions.unreadIndicator.firstUnseenSortId); -} - -+ (nullable NSNumber *)focusMessagePositionForThread:(TSThread *)thread - transaction:(YapDatabaseReadTransaction *)transaction - focusMessageId:(NSString *)focusMessageId -{ - OWSAssertDebug(thread); - OWSAssertDebug(transaction); - OWSAssertDebug(focusMessageId); - - YapDatabaseViewTransaction *databaseView = [transaction ext:TSMessageDatabaseViewExtensionName]; - - NSString *_Nullable group = nil; - NSUInteger index; - BOOL success = - [databaseView getGroup:&group index:&index forKey:focusMessageId inCollection:TSInteraction.collection]; - if (!success) { - // This might happen if the focus message has disappeared - // before this view could appear. - OWSFailDebug(@"failed to find focus message index."); - return nil; - } - if (![group isEqualToString:thread.uniqueId]) { - OWSFailDebug(@"focus message has invalid group."); - return nil; - } - NSUInteger count = [databaseView numberOfItemsInGroup:thread.uniqueId]; - if (index >= count) { - OWSFailDebug(@"focus message has invalid index."); - return nil; - } - NSUInteger position = (count - index) - 1; - return @(position); -} - -+ (BOOL)shouldShowGroupProfileBannerInThread:(TSThread *)thread blockingManager:(OWSBlockingManager *)blockingManager -{ - OWSAssertDebug(thread); - OWSAssertDebug(blockingManager); - - if (!thread.isGroupThread) { - return NO; - } - if ([OWSProfileManager.sharedManager isThreadInProfileWhitelist:thread]) { - return NO; - } - if (![OWSProfileManager.sharedManager hasLocalProfile]) { - return NO; - } - if ([blockingManager isThreadBlocked:thread]) { - return NO; - } - - BOOL hasUnwhitelistedMember = NO; - NSArray *blockedPhoneNumbers = [blockingManager blockedPhoneNumbers]; - for (NSString *recipientId in thread.recipientIdentifiers) { - if (![blockedPhoneNumbers containsObject:recipientId] - && ![OWSProfileManager.sharedManager isUserInProfileWhitelist:recipientId]) { - hasUnwhitelistedMember = YES; - break; - } - } - if (!hasUnwhitelistedMember) { - return NO; - } - return YES; -} - -+ (BOOL)addThreadToProfileWhitelistIfEmptyContactThread:(TSThread *)thread -{ - OWSAssertDebug(thread); - - if (thread.isGroupThread) { - return NO; - } - if ([OWSProfileManager.sharedManager isThreadInProfileWhitelist:thread]) { - return NO; - } - if (!thread.shouldThreadBeVisible) { - [OWSProfileManager.sharedManager addThreadToProfileWhitelist:thread]; - return YES; - } else { - return NO; - } -} - -#pragma mark - Delete Content - -+ (void)deleteAllContent -{ - OWSLogInfo(@""); - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [self removeAllObjectsInCollection:[TSThread collection] - class:[TSThread class] - transaction:transaction]; - [self removeAllObjectsInCollection:[TSInteraction collection] - class:[TSInteraction class] - transaction:transaction]; - [self removeAllObjectsInCollection:[TSAttachment collection] - class:[TSAttachment class] - transaction:transaction]; - @try { - [self removeAllObjectsInCollection:[SignalRecipient collection] - class:[SignalRecipient class] - transaction:transaction]; - } @catch (NSException *exception) { - // Do nothing - } - }]; - [TSAttachmentStream deleteAttachments]; -} - -+ (void)removeAllObjectsInCollection:(NSString *)collection - class:(Class) class - transaction:(YapDatabaseReadWriteTransaction *)transaction { - OWSAssertDebug(collection.length > 0); - OWSAssertDebug(class); - OWSAssertDebug(transaction); - - NSArray *_Nullable uniqueIds = [transaction allKeysInCollection:collection]; - if (!uniqueIds) { - OWSFailDebug(@"couldn't load uniqueIds for collection: %@.", collection); - return; - } - OWSLogInfo(@"Deleting %lu objects from: %@", (unsigned long)uniqueIds.count, collection); - NSUInteger count = 0; - for (NSString *uniqueId in uniqueIds) { - // We need to fetch each object, since [TSYapDatabaseObject removeWithTransaction:] sometimes does important - // work. - TSYapDatabaseObject *_Nullable object = [class fetchObjectWithUniqueID:uniqueId transaction:transaction]; - if (!object) { - OWSFailDebug(@"couldn't load object for deletion: %@.", collection); - continue; - } - [object removeWithTransaction:transaction]; - count++; - }; - OWSLogInfo(@"Deleted %lu/%lu objects from: %@", (unsigned long)count, (unsigned long)uniqueIds.count, collection); -} - -#pragma mark - Find Content - -+ (nullable TSInteraction *)findInteractionInThreadByTimestamp:(uint64_t)timestamp - authorId:(NSString *)authorId - threadUniqueId:(NSString *)threadUniqueId - transaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(timestamp > 0); - OWSAssertDebug(authorId.length > 0); - - NSString *localNumber = [TSAccountManager localNumber]; - if (localNumber.length < 1) { - OWSFailDebug(@"missing long number."); - return nil; - } - - NSArray *interactions = - [TSInteraction interactionsWithTimestamp:timestamp - filter:^(TSInteraction *interaction) { - NSString *_Nullable messageAuthorId = nil; - if ([interaction isKindOfClass:[TSIncomingMessage class]]) { - TSIncomingMessage *incomingMessage = (TSIncomingMessage *)interaction; - messageAuthorId = incomingMessage.authorId; - } else if ([interaction isKindOfClass:[TSOutgoingMessage class]]) { - messageAuthorId = localNumber; - } - if (messageAuthorId.length < 1) { - return NO; - } - - if (![authorId isEqualToString:messageAuthorId]) { - return NO; - } - if (![interaction.uniqueThreadId isEqualToString:threadUniqueId]) { - return NO; - } - return YES; - } - withTransaction:transaction]; - if (interactions.count < 1) { - return nil; - } - if (interactions.count > 1) { - // In case of collision, take the first. - OWSLogError(@"more than one matching interaction in thread."); - } - return interactions.firstObject; -} - -@end - -NS_ASSUME_NONNULL_END