diff --git a/Session/Signal/ConversationView/ConversationViewController.m b/Session/Signal/ConversationView/ConversationViewController.m index 91f8cfd1d..d6a51e945 100644 --- a/Session/Signal/ConversationView/ConversationViewController.m +++ b/Session/Signal/ConversationView/ConversationViewController.m @@ -65,6 +65,7 @@ #import #import #import +#import #import #import #import @@ -1477,7 +1478,7 @@ typedef enum : NSUInteger { UIAlertAction *deleteMessageAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_DELETE_TITLE", @"") style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) { - [message remove]; + [self remove:message]; }]; [actionSheet addAction:deleteMessageAction]; @@ -1497,6 +1498,14 @@ typedef enum : NSUInteger { [self presentAlert:actionSheet]; } +- (void)remove:(TSOutgoingMessage *)message +{ + [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [message removeWithTransaction:transaction]; + [LKStorage.shared cancelPendingMessageSendJobIfNeededForMessage:message.timestamp using:transaction]; + }]; +} + - (void)tappedCorruptedMessage:(TSErrorMessage *)message { NSString *alertMessage = [NSString diff --git a/Session/Signal/ConversationView/ConversationViewItem.m b/Session/Signal/ConversationView/ConversationViewItem.m index 6583ffce5..8ad9ff9d5 100644 --- a/Session/Signal/ConversationView/ConversationViewItem.m +++ b/Session/Signal/ConversationView/ConversationViewItem.m @@ -1084,26 +1084,37 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) - (void)deleteAction { - [self.interaction remove]; + [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [self.interaction removeWithTransaction:transaction]; + if (self.interaction.interactionType == OWSInteractionType_OutgoingMessage) { + [LKStorage.shared cancelPendingMessageSendJobIfNeededForMessage:self.interaction.timestamp using:transaction]; + } + }]; if (self.isGroupThread) { - // Skip if the thread is an RSS feed TSGroupThread *groupThread = (TSGroupThread *)self.interaction.thread; // Only allow deletion on incoming and outgoing messages OWSInteractionType interationType = self.interaction.interactionType; if (interationType != OWSInteractionType_IncomingMessage && interationType != OWSInteractionType_OutgoingMessage) return; - // Make sure it's a public chat message + // Make sure it's an open group message TSMessage *message = (TSMessage *)self.interaction; if (!message.isOpenGroupMessage) return; - - SNOpenGroup *publicChat = [LKStorage.shared getOpenGroupForThreadID:groupThread.uniqueId]; - if (publicChat == nil) return; + + // Get the open group + SNOpenGroup *openGroup = [LKStorage.shared getOpenGroupForThreadID:groupThread.uniqueId]; + if (openGroup == nil) return; + + // If it's an incoming message the user must have moderator status + if (self.interaction.interactionType == OWSInteractionType_IncomingMessage) { + NSString *userPublicKey = [LKStorage.shared getUserPublicKey]; + if (![SNOpenGroupAPI isUserModerator:userPublicKey forChannel:openGroup.channel onServer:openGroup.server]) { return; } + } // Delete the message - BOOL isSentByUser = (interationType == OWSInteractionType_OutgoingMessage); - [[SNOpenGroupAPI deleteMessageWithID:message.openGroupServerMessageID forGroup:publicChat.channel onServer:publicChat.server isSentByUser:isSentByUser].catch(^(NSError *error) { + BOOL wasSentByUser = (interationType == OWSInteractionType_OutgoingMessage); + [[SNOpenGroupAPI deleteMessageWithID:message.openGroupServerMessageID forGroup:openGroup.channel onServer:openGroup.server isSentByUser:wasSentByUser].catch(^(NSError *error) { // Roll back [self.interaction save]; }) retainUntilComplete]; diff --git a/Session/Signal/MessageActions.swift b/Session/Signal/MessageActions.swift index 03bdcb109..d049b7c20 100644 --- a/Session/Signal/MessageActions.swift +++ b/Session/Signal/MessageActions.swift @@ -13,23 +13,21 @@ protocol MessageActionsDelegate: class { } struct MessageActionBuilder { + static func reply(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction { return MenuAction(image: #imageLiteral(resourceName: "ic_reply"), title: NSLocalizedString("MESSAGE_ACTION_REPLY", comment: "Action sheet button title"), subtitle: nil, - block: { [weak delegate] (_) in - delegate?.messageActionsReplyToItem(conversationViewItem) - - }) + block: { [weak delegate] _ in delegate?.messageActionsReplyToItem(conversationViewItem) } + ) } static func copyText(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction { return MenuAction(image: #imageLiteral(resourceName: "ic_copy"), title: NSLocalizedString("MESSAGE_ACTION_COPY_TEXT", comment: "Action sheet button title"), subtitle: nil, - block: { (_) in - conversationViewItem.copyTextAction() - }) + block: { _ in conversationViewItem.copyTextAction() } + ) } static func copyPublicKey(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction { @@ -44,9 +42,8 @@ struct MessageActionBuilder { return MenuAction(image: #imageLiteral(resourceName: "ic_info"), title: NSLocalizedString("MESSAGE_ACTION_DETAILS", comment: "Action sheet button title"), subtitle: nil, - block: { [weak delegate] (_) in - delegate?.messageActionsShowDetailsForItem(conversationViewItem) - }) + block: { [weak delegate] _ in delegate?.messageActionsShowDetailsForItem(conversationViewItem) } + ) } static func report(_ conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction { @@ -61,27 +58,24 @@ struct MessageActionBuilder { return MenuAction(image: #imageLiteral(resourceName: "ic_trash"), title: NSLocalizedString("MESSAGE_ACTION_DELETE_MESSAGE", comment: "Action sheet button title"), subtitle: nil, - block: { (_) in - conversationViewItem.deleteAction() - }) + block: { _ in conversationViewItem.deleteAction() } + ) } static func copyMedia(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction { return MenuAction(image: #imageLiteral(resourceName: "ic_copy"), title: NSLocalizedString("MESSAGE_ACTION_COPY_MEDIA", comment: "Action sheet button title"), subtitle: nil, - block: { (_) in - conversationViewItem.copyMediaAction() - }) + block: { _ in conversationViewItem.copyMediaAction() } + ) } static func saveMedia(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction { return MenuAction(image: #imageLiteral(resourceName: "ic_download"), title: NSLocalizedString("MESSAGE_ACTION_SAVE_MEDIA", comment: "Action sheet button title"), subtitle: nil, - block: { (_) in - conversationViewItem.saveMediaAction() - }) + block: { _ in conversationViewItem.saveMediaAction() } + ) } } diff --git a/Session/View Controllers/HomeVC.swift b/Session/View Controllers/HomeVC.swift index a11422071..940766973 100644 --- a/Session/View Controllers/HomeVC.swift +++ b/Session/View Controllers/HomeVC.swift @@ -365,27 +365,28 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { guard let thread = self.thread(at: indexPath.row) else { return [] } - let publicChat = Storage.shared.getOpenGroup(for: thread.uniqueId!) + let openGroup = Storage.shared.getOpenGroup(for: thread.uniqueId!) let delete = UITableViewRowAction(style: .destructive, title: NSLocalizedString("TXT_DELETE_TITLE", comment: "")) { [weak self] _, _ in let alert = UIAlertController(title: NSLocalizedString("CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE", comment: ""), message: NSLocalizedString("CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE", comment: ""), preferredStyle: .alert) alert.addAction(UIAlertAction(title: NSLocalizedString("TXT_DELETE_TITLE", comment: ""), style: .destructive) { _ in - Storage.writeSync { transaction in - if let publicChat = publicChat { + Storage.write { transaction in + Storage.shared.cancelPendingMessageSendJobs(for: thread.uniqueId!, using: transaction) + if let openGroup = openGroup { var messageIDs: Set = [] thread.enumerateInteractions(with: transaction) { interaction, _ in messageIDs.insert(interaction.uniqueId!) } OWSPrimaryStorage.shared().updateMessageIDCollectionByPruningMessagesWithIDs(messageIDs, in: transaction) - transaction.removeObject(forKey: "\(publicChat.server).\(publicChat.channel)", inCollection: Storage.lastMessageServerIDCollection) - transaction.removeObject(forKey: "\(publicChat.server).\(publicChat.channel)", inCollection: Storage.lastDeletionServerIDCollection) - let _ = OpenGroupAPI.leave(publicChat.channel, on: publicChat.server) + transaction.removeObject(forKey: "\(openGroup.server).\(openGroup.channel)", inCollection: Storage.lastMessageServerIDCollection) + transaction.removeObject(forKey: "\(openGroup.server).\(openGroup.channel)", inCollection: Storage.lastDeletionServerIDCollection) + let _ = OpenGroupAPI.leave(openGroup.channel, on: openGroup.server) thread.removeAllThreadInteractions(with: transaction) thread.remove(with: transaction) } else if let thread = thread as? TSGroupThread, thread.usesSharedSenderKeys == true { let groupID = thread.groupModel.groupId let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID) let _ = MessageSender.leave(groupPublicKey, using: transaction).ensure { - Storage.writeSync { transaction in + Storage.write { transaction in thread.removeAllThreadInteractions(with: transaction) thread.remove(with: transaction) } diff --git a/SessionMessagingKit/Database/Storage+Jobs.swift b/SessionMessagingKit/Database/Storage+Jobs.swift index bb1e00f3e..8b0bf3315 100644 --- a/SessionMessagingKit/Database/Storage+Jobs.swift +++ b/SessionMessagingKit/Database/Storage+Jobs.swift @@ -28,7 +28,23 @@ extension Storage { transaction.removeAllObjects(inCollection: type.collection) } - public func cancelPendingMessageSendJobs(for threadID: String, using transaction: YapDatabaseReadWriteTransaction) { + @objc(cancelPendingMessageSendJobIfNeededForMessage:using:) + public func cancelPendingMessageSendJobIfNeeded(for tsMessageTimestamp: UInt64, using transaction: YapDatabaseReadWriteTransaction) { + var attachmentUploadJobKeys: [String] = [] + transaction.enumerateRows(inCollection: AttachmentUploadJob.collection) { key, object, _, _ in + guard let job = object as? AttachmentUploadJob, job.message.sentTimestamp == tsMessageTimestamp else { return } + attachmentUploadJobKeys.append(key) + } + var messageSendJobKeys: [String] = [] + transaction.enumerateRows(inCollection: MessageSendJob.collection) { key, object, _, _ in + guard let job = object as? MessageSendJob, job.message.sentTimestamp == tsMessageTimestamp else { return } + messageSendJobKeys.append(key) + } + transaction.removeObjects(forKeys: attachmentUploadJobKeys, inCollection: AttachmentUploadJob.collection) + transaction.removeObjects(forKeys: messageSendJobKeys, inCollection: MessageSendJob.collection) + } + + @objc public func cancelPendingMessageSendJobs(for threadID: String, using transaction: YapDatabaseReadWriteTransaction) { var attachmentUploadJobKeys: [String] = [] transaction.enumerateRows(inCollection: AttachmentUploadJob.collection) { key, object, _, _ in guard let job = object as? AttachmentUploadJob, job.threadID == threadID else { return } @@ -68,4 +84,12 @@ extension Storage { public func resumeMessageSendJobIfNeeded(_ messageSendJobID: String) { getMessageSendJob(for: messageSendJobID)?.execute() } + + public func isJobCanceled(_ job: Job) -> Bool { + var result = true + Storage.read { transaction in + result = !transaction.hasObject(forKey: job.id!, inCollection: type(of: job).collection) + } + return result + } } diff --git a/SessionMessagingKit/Database/Storage+Shared.swift b/SessionMessagingKit/Database/Storage+Shared.swift index e698b0c3d..426ffd500 100644 --- a/SessionMessagingKit/Database/Storage+Shared.swift +++ b/SessionMessagingKit/Database/Storage+Shared.swift @@ -14,7 +14,7 @@ extension Storage { Storage.write(with: { work($0) }, completion: completion) } - public func getUserPublicKey() -> String? { + @objc public func getUserPublicKey() -> String? { return OWSIdentityManager.shared().identityKeyPair()?.hexEncodedPublicKey } diff --git a/SessionMessagingKit/Jobs/JobQueue.swift b/SessionMessagingKit/Jobs/JobQueue.swift index e73e12530..09bb07d61 100644 --- a/SessionMessagingKit/Jobs/JobQueue.swift +++ b/SessionMessagingKit/Jobs/JobQueue.swift @@ -51,6 +51,7 @@ public final class JobQueue : NSObject, JobDelegate { public func handleJobFailed(_ job: Job, with error: Error) { job.failureCount += 1 let storage = Configuration.shared.storage + guard !storage.isJobCanceled(job) else { return SNLog("\(type(of: job)) canceled.") } storage.withAsync({ transaction in storage.persist(job, using: transaction) }, completion: { // Intentionally capture self @@ -62,7 +63,7 @@ public final class JobQueue : NSObject, JobDelegate { }) } else { let retryInterval = self.getRetryInterval(for: job) - SNLog("Job failed; scheduling retry.") + SNLog("\(type(of: job)) failed; scheduling retry (failure count is \(job.failureCount)).") Timer.scheduledTimer(timeInterval: retryInterval, target: self, selector: #selector(self.retry(_:)), userInfo: job, repeats: false) } }) @@ -96,8 +97,8 @@ public final class JobQueue : NSObject, JobDelegate { } @objc private func retry(_ timer: Timer) { - SNLog("Retrying job.") guard let job = timer.userInfo as? Job else { return } + SNLog("Retrying \(type(of: job)).") job.execute() } } diff --git a/SessionMessagingKit/Jobs/MessageSendJob.swift b/SessionMessagingKit/Jobs/MessageSendJob.swift index 169a9cb67..893142ec1 100644 --- a/SessionMessagingKit/Jobs/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/MessageSendJob.swift @@ -1,7 +1,5 @@ import SessionUtilitiesKit -// TODO: Cancel when a message/conversation is deleted - @objc(SNMessageSendJob) public final class MessageSendJob : NSObject, Job, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility public let message: Message @@ -63,6 +61,9 @@ public final class MessageSendJob : NSObject, Job, NSCoding { // NSObject/NSCodi // MARK: Running public func execute() { + if Double.random(in: 0..<1) > 0.01 { + return handleFailure(error: MessageSender.Error.noThread) + } let storage = Configuration.shared.storage if let message = message as? VisibleMessage { let attachments = message.attachmentIDs.compactMap { TSAttachmentStream.fetch(uniqueId: $0) } diff --git a/SessionMessagingKit/Storage.swift b/SessionMessagingKit/Storage.swift index 926aebc11..0f752fe00 100644 --- a/SessionMessagingKit/Storage.swift +++ b/SessionMessagingKit/Storage.swift @@ -34,6 +34,7 @@ public protocol SessionMessagingKitStorageProtocol { func getAttachmentUploadJob(for attachmentID: String) -> AttachmentUploadJob? func getMessageSendJob(for messageSendJobID: String) -> MessageSendJob? func resumeMessageSendJobIfNeeded(_ messageSendJobID: String) + func isJobCanceled(_ job: Job) -> Bool // MARK: - Authorization