Cancel message send job(s) if associated message/thread is deleted

This commit is contained in:
nielsandriesse 2020-11-27 10:08:46 +11:00
parent 2fa3a7edb7
commit c59fe05f8e
9 changed files with 83 additions and 41 deletions

View file

@ -65,6 +65,7 @@
#import <SessionMessagingKit/TSGroupModel.h>
#import <SessionMessagingKit/TSInvalidIdentityKeyReceivingErrorMessage.h>
#import <SessionMessagingKit/TSQuotedMessage.h>
#import <SessionMessagingKit/SessionMessagingKit-Swift.h>
#import <YapDatabase/YapDatabase.h>
#import <YapDatabase/YapDatabaseAutoView.h>
#import <YapDatabase/YapDatabaseViewChange.h>
@ -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

View file

@ -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];

View file

@ -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() }
)
}
}

View file

@ -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<String> = []
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)
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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()
}
}

View file

@ -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) }

View file

@ -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