mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Cancel message send job(s) if associated message/thread is deleted
This commit is contained in:
parent
2fa3a7edb7
commit
c59fe05f8e
9 changed files with 83 additions and 41 deletions
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue