diff --git a/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift b/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift index a66935fac..b078fe781 100644 --- a/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift +++ b/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift @@ -58,67 +58,75 @@ public enum GroupLeavingJob: JobExecutor { .receive(on: queue) .sinkUntilComplete( receiveCompletion: { result in - switch result { - case .failure: - Storage.shared.writeAsync { db in - try Interaction - .filter(id: job.interactionId) - .updateAll( - db, - [ - Interaction.Columns.variant - .set(to: Interaction.Variant.infoClosedGroupCurrentUserErrorLeaving), - Interaction.Columns.body.set(to: "group_unable_to_leave".localized()) - ] - ) + let failureChanges: [ConfigColumnAssignment] = [ + Interaction.Columns.variant + .set(to: Interaction.Variant.infoClosedGroupCurrentUserErrorLeaving), + Interaction.Columns.body.set(to: "group_unable_to_leave".localized()) + ] + let successfulChanges: [ConfigColumnAssignment] = [ + Interaction.Columns.variant + .set(to: Interaction.Variant.infoClosedGroupCurrentUserLeft), + Interaction.Columns.body.set(to: "GROUP_YOU_LEFT".localized()) + ] + + // Handle the appropriate response + Storage.shared.writeAsync { db in + // If it failed due to one of these errors then clear out any associated data (as somehow + // the 'SessionThread' exists but not the data required to send the 'MEMBER_LEFT' message + // which would leave the user in a state where they can't leave the group) + let errorsToSucceed: [MessageSenderError] = [ + .invalidClosedGroupUpdate, + .noKeyPair + ] + let shouldSucceed: Bool = { + switch result { + case .failure(let error as MessageSenderError): return errorsToSucceed.contains(error) + case .failure: return false + default: return true } - success(job, false) - - case .finished: - Storage.shared.writeAsync { db in - // Update the group (if the admin leaves the group is disbanded) - let currentUserPublicKey: String = getUserHexEncodedPublicKey(db) - let wasAdminUser: Bool = GroupMember - .filter(GroupMember.Columns.groupId == threadId) - .filter(GroupMember.Columns.profileId == currentUserPublicKey) - .filter(GroupMember.Columns.role == GroupMember.Role.admin) - .isNotEmpty(db) - - if wasAdminUser { - try GroupMember - .filter(GroupMember.Columns.groupId == threadId) - .deleteAll(db) - } - else { - try GroupMember - .filter(GroupMember.Columns.groupId == threadId) - .filter(GroupMember.Columns.profileId == currentUserPublicKey) - .deleteAll(db) - } - - // Update the transaction - try Interaction - .filter(id: interactionId) - .updateAll( - db, - [ - Interaction.Columns.variant - .set(to: Interaction.Variant.infoClosedGroupCurrentUserLeft), - Interaction.Columns.body.set(to: "GROUP_YOU_LEFT".localized()) - ] - ) - - // Clear out the group info as needed - try ClosedGroup.removeKeysAndUnsubscribe( - db, - threadId: threadId, - removeGroupData: details.deleteThread, - calledFromConfigHandling: false - ) - } - - success(job, false) + }() + + // Update the transaction + try Interaction + .filter(id: interactionId) + .updateAll( + db, + (shouldSucceed ? successfulChanges : failureChanges) + ) + + // If we succeed in leaving then we should try to clear the group data + guard shouldSucceed else { return } + + // Update the group (if the admin leaves the group is disbanded) + let currentUserPublicKey: String = getUserHexEncodedPublicKey(db) + let wasAdminUser: Bool = GroupMember + .filter(GroupMember.Columns.groupId == threadId) + .filter(GroupMember.Columns.profileId == currentUserPublicKey) + .filter(GroupMember.Columns.role == GroupMember.Role.admin) + .isNotEmpty(db) + + if wasAdminUser { + try GroupMember + .filter(GroupMember.Columns.groupId == threadId) + .deleteAll(db) + } + else { + try GroupMember + .filter(GroupMember.Columns.groupId == threadId) + .filter(GroupMember.Columns.profileId == currentUserPublicKey) + .deleteAll(db) + } + + // Clear out the group info as needed + try ClosedGroup.removeKeysAndUnsubscribe( + db, + threadId: threadId, + removeGroupData: details.deleteThread, + calledFromConfigHandling: false + ) } + + success(job, false) } ) } diff --git a/SessionMessagingKit/Sending & Receiving/Errors/MessageSenderError.swift b/SessionMessagingKit/Sending & Receiving/Errors/MessageSenderError.swift index 0ee8cd684..12cb06900 100644 --- a/SessionMessagingKit/Sending & Receiving/Errors/MessageSenderError.swift +++ b/SessionMessagingKit/Sending & Receiving/Errors/MessageSenderError.swift @@ -2,7 +2,7 @@ import Foundation -public enum MessageSenderError: LocalizedError { +public enum MessageSenderError: LocalizedError, Equatable { case invalidMessage case protoConversionFailed case noUserX25519KeyPair @@ -44,4 +44,26 @@ public enum MessageSenderError: LocalizedError { case .other(let error): return error.localizedDescription } } + + public static func == (lhs: MessageSenderError, rhs: MessageSenderError) -> Bool { + switch (lhs, rhs) { + case (.invalidMessage, .invalidMessage): return true + case (.protoConversionFailed, .protoConversionFailed): return true + case (.noUserX25519KeyPair, .noUserX25519KeyPair): return true + case (.noUserED25519KeyPair, .noUserED25519KeyPair): return true + case (.signingFailed, .signingFailed): return true + case (.encryptionFailed, .encryptionFailed): return true + case (.noUsername, .noUsername): return true + case (.attachmentsNotUploaded, .attachmentsNotUploaded): return true + case (.noThread, .noThread): return true + case (.noKeyPair, .noKeyPair): return true + case (.invalidClosedGroupUpdate, .invalidClosedGroupUpdate): return true + + case (.other(let lhsError), .other(let rhsError)): + // Not ideal but the best we can do + return (lhsError.localizedDescription == rhsError.localizedDescription) + + default: return false + } + } }