From a2451b8f32674dc7b8bb9e6105af2a3a717de05f Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Mon, 19 Jul 2021 21:35:52 +0300 Subject: [PATCH] Use Combine for MessageGateway --- FlowCrypt.xcodeproj/project.pbxproj | 4 + .../Compose/ComposeViewController.swift | 90 ++++++++++--------- FlowCrypt/Extensions/CombineExtensions.swift | 20 +++++ .../Message Gateway/GmailService+send.swift | 13 ++- .../Message Gateway/Imap+send.swift | 18 ++-- .../Message Gateway/MessageGateway.swift | 4 +- .../Backup Services/BackupService.swift | 19 +++- 7 files changed, 108 insertions(+), 60 deletions(-) create mode 100644 FlowCrypt/Extensions/CombineExtensions.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 1ac6b9885..8a032bd68 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -101,6 +101,7 @@ 9F5C2A99257E94E900DE9B4B /* Gmail+MessageOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5C2A98257E94E900DE9B4B /* Gmail+MessageOperations.swift */; }; 9F6EE1552597399D0059BA51 /* BackupProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6EE1542597399D0059BA51 /* BackupProvider.swift */; }; 9F6EE17B2598F9FA0059BA51 /* Gmail+Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6EE17A2598F9FA0059BA51 /* Gmail+Backup.swift */; }; + 9F6F603026A5F9F900C625C7 /* CombineExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6F602F26A5F9F900C625C7 /* CombineExtensions.swift */; }; 9F716308234FC73E0031645E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9F71630A234FC73E0031645E /* Localizable.strings */; }; 9F7920F52667CEF100DA3D80 /* PassPraseSaveable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7920F42667CEF100DA3D80 /* PassPraseSaveable.swift */; }; 9F79228826696B0200DA3D80 /* PassPhraseService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F79228726696B0200DA3D80 /* PassPhraseService.swift */; }; @@ -502,6 +503,7 @@ 9F696294236091F4003712E1 /* SignInDescriptionNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInDescriptionNode.swift; sourceTree = ""; }; 9F6EE1542597399D0059BA51 /* BackupProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupProvider.swift; sourceTree = ""; }; 9F6EE17A2598F9FA0059BA51 /* Gmail+Backup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Gmail+Backup.swift"; sourceTree = ""; }; + 9F6F602F26A5F9F900C625C7 /* CombineExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineExtensions.swift; sourceTree = ""; }; 9F716301234FC6950031645E /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/LaunchScreen.strings; sourceTree = ""; }; 9F716304234FC7200031645E /* LocalizationExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationExtensions.swift; sourceTree = ""; }; 9F716309234FC73E0031645E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; @@ -912,6 +914,7 @@ 9F31AB9D232BF2A600CF87EA /* UIColorExtension.swift */, D2D27B78248A8694007346FA /* BigIntExtension.swift */, 21C7DF0426697DA500C44800 /* PromiseKitExtension.swift */, + 9F6F602F26A5F9F900C625C7 /* CombineExtensions.swift */, ); path = Extensions; sourceTree = ""; @@ -2451,6 +2454,7 @@ C132B9D91EC30E1D00763715 /* InboxViewController.swift in Sources */, 9F56BD2C23438A8500A7371A /* Imap+messages.swift in Sources */, C132B9CB1EC2DE6400763715 /* GeneralConstants.swift in Sources */, + 9F6F603026A5F9F900C625C7 /* CombineExtensions.swift in Sources */, 5ADEDCBE23A4363700EC495E /* KeyDetailInfoViewController.swift in Sources */, D20D3C752520AB9A00D4AA9A /* BackupService.swift in Sources */, C192421F1EC48B6900C3D251 /* SetupBackupsViewController.swift in Sources */, diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index c9d9941e9..a03d2b742 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -3,9 +3,9 @@ // import AsyncDisplayKit +import Combine import FlowCryptCommon import FlowCryptUI -import Promises /** * View controller to compose the message and send it @@ -49,6 +49,7 @@ final class ComposeViewController: TableNodeViewController { case subject, subjectDivider, text } + private var cancellable = Set() private let messageSender: MessageGateway private let notificationCenter: NotificationCenter private let dataService: KeyStorageType @@ -120,6 +121,7 @@ final class ComposeViewController: TableNodeViewController { // temporary disable search contacts - https://github.com/FlowCrypt/flowcrypt-ios/issues/217 // showScopeAlertIfNeeded() + cancellable.forEach { $0.cancel() } } deinit { @@ -216,61 +218,65 @@ extension ComposeViewController { extension ComposeViewController { private func sendMessage() { view.endEditing(true) + guard isInputValid() else { return } showSpinner("sending_title".localized) - Promise { [weak self] in - try awaitPromise(self!.encryptAndSendMessage()) - }.then(on: .main) { [weak self] sent in - if sent { // else it must have shown error to user - self?.handleSuccessfullySentMessage() - } - }.catch(on: .main) { [weak self] error in - self?.showAlert(error: error, message: "compose_error".localized) - } + encryptAndSendMessage() } - private func encryptAndSendMessage() -> Promise { - Promise { [weak self] () -> Bool in - guard let self = self else { return false } + // TODO: - Refactor send message checks. https://github.com/FlowCrypt/flowcrypt-ios/issues/399 + private func encryptAndSendMessage() { + let recipients = contextToSend.recipients - let recipients = self.contextToSend.recipients - - guard recipients.isNotEmpty else { - assertionFailure("Recipients should not be empty. Fail in checking") - return false - } + guard recipients.isNotEmpty else { + showAlert(message: "Recipients should not be empty. Fail in checking") + return + } - guard let text = self.contextToSend.message else { - assertionFailure("Text and Email should not be nil at this point. Fail in checking") - return false - } + guard let text = self.contextToSend.message else { + showAlert(message: "Text and Email should not be nil at this point. Fail in checking") + return + } - let subject = self.input.subjectReplyTitle - ?? self.contextToSend.subject - ?? "(no subject)" + let subject = self.input.subjectReplyTitle + ?? self.contextToSend.subject + ?? "(no subject)" - guard let myPubKey = self.dataService.publicKey() else { - self.showAlert(message: "compose_no_pub_sender".localized) - return false - } + guard let myPubKey = self.dataService.publicKey() else { + self.showAlert(message: "compose_no_pub_sender".localized) + return + } - guard let allRecipientPubs = self.getPubKeys(for: recipients) else { - return false - } + guard let allRecipientPubs = getPubKeys(for: recipients) else { + showAlert(message: "Public key is missin") + return + } - let encrypted = self.encryptMsg( - pubkeys: allRecipientPubs + [myPubKey], - subject: subject, - message: text, - to: recipients.map(\.email) - ) + showSpinner() - try awaitPromise(self.messageSender.sendMail(mime: encrypted.mimeEncoded)) + let encrypted = self.encryptMsg( + pubkeys: allRecipientPubs + [myPubKey], + subject: subject, + message: text, + to: recipients.map(\.email) + ) - return true - } + messageSender + .sendMail(mime: encrypted.mimeEncoded) + .sink( + receiveCompletion: { [weak self] result in + guard let error = result.getError() else { + return + } + + self?.showAlert(error: error, message: "compose_error".localized) + }, + receiveValue: { [weak self] in + self?.handleSuccessfullySentMessage() + }) + .store(in: &cancellable) } private func getPubKeys(for recepients: [Recipient]) -> [String]? { diff --git a/FlowCrypt/Extensions/CombineExtensions.swift b/FlowCrypt/Extensions/CombineExtensions.swift new file mode 100644 index 000000000..45430eec8 --- /dev/null +++ b/FlowCrypt/Extensions/CombineExtensions.swift @@ -0,0 +1,20 @@ +// +// CombineExtensions.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 19.07.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import Combine + +extension Subscribers.Completion { + func getError() -> Error? { + switch self { + case .failure(let error): + return error + case .finished: + return nil + } + } +} diff --git a/FlowCrypt/Functionality/Mail Provider/Message Gateway/GmailService+send.swift b/FlowCrypt/Functionality/Mail Provider/Message Gateway/GmailService+send.swift index aaefe76de..8a6ddb7ea 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Gateway/GmailService+send.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Gateway/GmailService+send.swift @@ -6,16 +6,16 @@ // Copyright © 2020 FlowCrypt Limited. All rights reserved. // +import Combine import Foundation import GoogleAPIClientForREST import GTMSessionFetcher -import Promises extension GmailService: MessageGateway { - func sendMail(mime: Data) -> Promise { - Promise { resolve, reject in + func sendMail(mime: Data) -> Future { + Future { promise in guard let raw = GTLREncodeBase64(mime) else { - return reject(GmailServiceError.messageEncode) + return promise(.failure(GmailServiceError.messageEncode)) } let gtlMessage = GTLRGmail_Message() @@ -29,10 +29,9 @@ extension GmailService: MessageGateway { self.gmailService.executeQuery(querySend) { _, _, error in if let error = error { - reject(GmailServiceError.providerError(error)) - return + return promise(.failure(GmailServiceError.providerError(error))) } - resolve(()) + promise(.success(())) } } } diff --git a/FlowCrypt/Functionality/Mail Provider/Message Gateway/Imap+send.swift b/FlowCrypt/Functionality/Mail Provider/Message Gateway/Imap+send.swift index 1323301bc..ac6907f0b 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Gateway/Imap+send.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Gateway/Imap+send.swift @@ -2,16 +2,20 @@ // © 2017-2019 FlowCrypt Limited. All rights reserved. // +import Combine import Foundation -import Promises extension Imap: MessageGateway { - func sendMail(mime: Data) -> Promise { - Promise { [weak self] resolve, reject in - guard let self = self else { return reject(AppErr.nilSelf) } - self.smtpSess? - .sendOperation(with: mime) - .start(self.finalizeVoid("send", resolve, reject, retry: { self.sendMail(mime: mime) })) + func sendMail(mime: Data) -> Future { + Future { [smtpSess] promise in + smtpSess?.sendOperation(with: mime) + .start { error in + if let error = error { + promise(.failure(error)) + } else { + promise(.success(())) + } + } } } } diff --git a/FlowCrypt/Functionality/Mail Provider/Message Gateway/MessageGateway.swift b/FlowCrypt/Functionality/Mail Provider/Message Gateway/MessageGateway.swift index abadd0182..0c213e0b7 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Gateway/MessageGateway.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Gateway/MessageGateway.swift @@ -6,9 +6,9 @@ // Copyright © 2020 FlowCrypt Limited. All rights reserved. // +import Combine import Foundation -import Promises protocol MessageGateway { - func sendMail(mime: Data) -> Promise + func sendMail(mime: Data) -> Future } diff --git a/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift b/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift index 55d78a1f5..49af60786 100644 --- a/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift +++ b/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift @@ -6,6 +6,7 @@ // Copyright © 2020 FlowCrypt Limited. All rights reserved. // +import Combine import Promises import UIKit @@ -13,6 +14,7 @@ final class BackupService { let backupProvider: BackupProvider let core: Core let messageSender: MessageGateway + private var cancellable = Set() init( backupProvider: BackupProvider = MailProvider.shared.backupProvider, @@ -44,7 +46,7 @@ extension BackupService: BackupServiceType { } func backupToInbox(keys: [KeyDetails], for userId: UserId) -> Promise { - Promise { [weak self] () -> Void in + Promise { [weak self] (resolve, reject) -> Void in guard let self = self else { throw AppErr.nilSelf } let isFullyEncryptedKeys = keys.map(\.isFullyDecrypted).contains(false) @@ -72,7 +74,20 @@ extension BackupService: BackupServiceType { atts: attachments ) let backupEmail = try self.core.composeEmail(msg: message, fmt: .plain, pubKeys: nil) - try awaitPromise(self.messageSender.sendMail(mime: backupEmail.mimeEncoded)) + + self.messageSender + .sendMail(mime: backupEmail.mimeEncoded) + .sink( + receiveCompletion: { result in + guard let error = result.getError() else { + return + } + reject(error) + }, + receiveValue: { + resolve(()) + }) + .store(in: &self.cancellable) } }