From 8fa61fa0aa171071b8acd886f7ce53ae78ca2640 Mon Sep 17 00:00:00 2001 From: Bruno Muniz Azevedo Filho <bruno@elixxir.io> Date: Wed, 13 Jul 2022 03:57:47 -0300 Subject: [PATCH] Finishing backup sftp --- Package.swift | 1 + Sources/App/AppDelegate.swift | 2 +- Sources/App/DependencyRegistrator.swift | 2 - .../Controllers/BackupConfigController.swift | 2 +- .../Controllers/BackupSFTPController.swift | 80 --------------- .../Controllers/BackupSetupController.swift | 5 + .../Coordinator/BackupCoordinator.swift | 11 --- .../BackupFeature/Service/BackupService.swift | 40 ++++---- .../BackupFeature/Views/BackupSetupView.swift | 32 +++--- .../Controllers/RestoreListController.swift | 8 +- .../Coordinator/RestoreCoordinator.swift | 20 ---- .../ViewModels/RestoreListViewModel.swift | 34 ++++++- .../ViewModels/RestoreSFTPViewModel.swift | 81 --------------- .../ViewModels/RestoreViewModel.swift | 2 +- .../Views/RestoreSFTPView.swift | 76 -------------- .../SFTPController.swift} | 28 +++--- Sources/SFTPFeature/SFTPService.swift | 99 ++++++++++--------- .../SFTPView.swift} | 2 +- .../SFTPViewModel.swift} | 35 ++++--- .../Resources/en.lproj/Localizable.strings | 2 +- 20 files changed, 170 insertions(+), 392 deletions(-) delete mode 100644 Sources/BackupFeature/Controllers/BackupSFTPController.swift delete mode 100644 Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift delete mode 100644 Sources/RestoreFeature/Views/RestoreSFTPView.swift rename Sources/{RestoreFeature/Controllers/RestoreSFTPController.swift => SFTPFeature/SFTPController.swift} (81%) rename Sources/{BackupFeature/Views/BackupSFTPView.swift => SFTPFeature/SFTPView.swift} (98%) rename Sources/{BackupFeature/ViewModels/BackupSFTPViewModel.swift => SFTPFeature/SFTPViewModel.swift} (63%) diff --git a/Package.swift b/Package.swift index 69613b7a..0e7bb614 100644 --- a/Package.swift +++ b/Package.swift @@ -216,6 +216,7 @@ let package = Package( .target( name: "SFTPFeature", dependencies: [ + "HUD", "Shared", "Keychain", "InputField", diff --git a/Sources/App/AppDelegate.swift b/Sources/App/AppDelegate.swift index 3675b450..72a7f682 100644 --- a/Sources/App/AppDelegate.swift +++ b/Sources/App/AppDelegate.swift @@ -1,8 +1,8 @@ import UIKit import BackgroundTasks -import XXModels import Theme +import XXModels import XXLogger import Defaults import Integration diff --git a/Sources/App/DependencyRegistrator.swift b/Sources/App/DependencyRegistrator.swift index 471e1008..8caed374 100644 --- a/Sources/App/DependencyRegistrator.swift +++ b/Sources/App/DependencyRegistrator.swift @@ -122,7 +122,6 @@ struct DependencyRegistrator { container.register( BackupCoordinator( - sftpFactory: BackupSFTPController.init, passphraseFactory: BackupPassphraseController.init(_:_:) ) as BackupCoordinating) @@ -165,7 +164,6 @@ struct DependencyRegistrator { RestoreCoordinator( successFactory: RestoreSuccessController.init, chatListFactory: ChatListController.init, - sftpFactory: RestoreSFTPController.init(_:), restoreFactory: RestoreController.init(_:_:), passphraseFactory: RestorePassphraseController.init(_:) ) as RestoreCoordinating) diff --git a/Sources/BackupFeature/Controllers/BackupConfigController.swift b/Sources/BackupFeature/Controllers/BackupConfigController.swift index 4a2b5974..a901e6b9 100644 --- a/Sources/BackupFeature/Controllers/BackupConfigController.swift +++ b/Sources/BackupFeature/Controllers/BackupConfigController.swift @@ -122,7 +122,7 @@ final class BackupConfigController: UIViewController { screenView.sftpButton .publisher(for: .touchUpInside) - .sink { [unowned self] in coordinator.toSFTP(from: self) } + .sink { [unowned self] in viewModel.didTapService(.sftp, self) } .store(in: &cancellables) screenView.iCloudButton diff --git a/Sources/BackupFeature/Controllers/BackupSFTPController.swift b/Sources/BackupFeature/Controllers/BackupSFTPController.swift deleted file mode 100644 index f0f85186..00000000 --- a/Sources/BackupFeature/Controllers/BackupSFTPController.swift +++ /dev/null @@ -1,80 +0,0 @@ -import HUD -import UIKit -import Combine -import DependencyInjection - -public final class BackupSFTPController: UIViewController { - @Dependency private var hud: HUDType - - lazy private var screenView = BackupSFTPView() - - private let viewModel = BackupSFTPViewModel() - private var cancellables = Set<AnyCancellable>() - - public override func loadView() { - view = screenView - } - - public override func viewDidLoad() { - super.viewDidLoad() - setupNavigationBar() - setupBindings() - } - - private func setupNavigationBar() { - navigationItem.backButtonTitle = "" - - let back = UIButton.back() - back.addTarget(self, action: #selector(didTapBack), for: .touchUpInside) - - navigationItem.leftBarButtonItem = UIBarButtonItem( - customView: UIStackView(arrangedSubviews: [back]) - ) - } - - private func setupBindings() { - viewModel.hudPublisher - .receive(on: DispatchQueue.main) - .sink { [hud] in hud.update(with: $0) } - .store(in: &cancellables) - - viewModel.popPublisher - .receive(on: DispatchQueue.main) - .sink { [unowned self] in - navigationController?.popViewController(animated: true) - }.store(in: &cancellables) - - screenView.hostField - .textPublisher - .receive(on: DispatchQueue.main) - .sink { [unowned self] in viewModel.didEnterHost($0) } - .store(in: &cancellables) - - screenView.usernameField - .textPublisher - .receive(on: DispatchQueue.main) - .sink { [unowned self] in viewModel.didEnterUsername($0) } - .store(in: &cancellables) - - screenView.passwordField - .textPublisher - .receive(on: DispatchQueue.main) - .sink { [unowned self] in viewModel.didEnterPassword($0) } - .store(in: &cancellables) - - viewModel.statePublisher - .receive(on: DispatchQueue.main) - .map(\.isButtonEnabled) - .sink { [unowned self] in screenView.loginButton.isEnabled = $0 } - .store(in: &cancellables) - - screenView.loginButton - .publisher(for: .touchUpInside) - .sink { [unowned self] in viewModel.didTapLogin() } - .store(in: &cancellables) - } - - @objc private func didTapBack() { - navigationController?.popViewController(animated: true) - } -} diff --git a/Sources/BackupFeature/Controllers/BackupSetupController.swift b/Sources/BackupFeature/Controllers/BackupSetupController.swift index 49e05a2b..e22de517 100644 --- a/Sources/BackupFeature/Controllers/BackupSetupController.swift +++ b/Sources/BackupFeature/Controllers/BackupSetupController.swift @@ -37,5 +37,10 @@ final class BackupSetupController: UIViewController { .publisher(for: .touchUpInside) .sink { [unowned self] in viewModel.didTapService(.icloud, self) } .store(in: &cancellables) + + screenView.sftpButton + .publisher(for: .touchUpInside) + .sink { [unowned self] in viewModel.didTapService(.sftp, self) } + .store(in: &cancellables) } } diff --git a/Sources/BackupFeature/Coordinator/BackupCoordinator.swift b/Sources/BackupFeature/Coordinator/BackupCoordinator.swift index c79cdcdb..a49b51bd 100644 --- a/Sources/BackupFeature/Coordinator/BackupCoordinator.swift +++ b/Sources/BackupFeature/Coordinator/BackupCoordinator.swift @@ -8,8 +8,6 @@ public protocol BackupCoordinating { from: UIViewController ) - func toSFTP(from: UIViewController) - func toPassphrase( from: UIViewController, cancelClosure: @escaping EmptyClosure, @@ -18,27 +16,18 @@ public protocol BackupCoordinating { } public struct BackupCoordinator: BackupCoordinating { - var pushPresenter: Presenting = PushPresenter() var bottomPresenter: Presenting = BottomPresenter() - var sftpFactory: () -> UIViewController var passphraseFactory: (@escaping EmptyClosure, @escaping StringClosure) -> UIViewController public init( - sftpFactory: @escaping () -> UIViewController, passphraseFactory: @escaping (@escaping EmptyClosure, @escaping StringClosure) -> UIViewController ) { - self.sftpFactory = sftpFactory self.passphraseFactory = passphraseFactory } } public extension BackupCoordinator { - func toSFTP(from parent: UIViewController) { - let screen = sftpFactory() - pushPresenter.present(screen, from: parent) - } - func toDrawer( _ screen: UIViewController, from parent: UIViewController diff --git a/Sources/BackupFeature/Service/BackupService.swift b/Sources/BackupFeature/Service/BackupService.swift index f1fbd846..54a3a5f6 100644 --- a/Sources/BackupFeature/Service/BackupService.swift +++ b/Sources/BackupFeature/Service/BackupService.swift @@ -154,7 +154,14 @@ extension BackupService { }.store(in: &cancellables) } case .sftp: - break + if !sftpService.isAuthorized() { + sftpService.authorizeFlow((screen, { [weak self] in + guard let self = self else { return } + screen.navigationController?.popViewController(animated: true) + self.refreshConnections() + self.refreshBackups() + })) + } } } } @@ -209,25 +216,20 @@ extension BackupService { } if sftpService.isAuthorized() { - if let keychain = try? DependencyInjection.Container.shared.resolve() as KeychainHandling, - let pwd = try? keychain.get(key: .pwd), - let host = try? keychain.get(key: .host), - let username = try? keychain.get(key: .username) { - - let completion: SFTPFetchResult = { [weak settings] result in - guard let settings = settings else { return } - - switch result { - case .success(let backupSettings): - settings.value.backups[.sftp] = backupSettings?.backup - case .failure(let error): - print(error.localizedDescription) - } + sftpService.fetchMetadata({ [weak settings] in + guard let settings = settings else { return } + + guard let metadata = try? $0.get()?.backup else { + settings.value.backups[.sftp] = nil + return } - let authParams = SFTPAuthParams(host, username, pwd) - sftpService.fetch((authParams, completion)) - } + settings.value.backups[.sftp] = Backup( + id: metadata.id, + date: metadata.date, + size: metadata.size + ) + }) } if dropboxService.isAuthorized() { @@ -329,7 +331,7 @@ extension BackupService { } case .sftp: do { - try sftpService.upload(url) + try sftpService.uploadBackup(url) } catch { print(error.localizedDescription) } diff --git a/Sources/BackupFeature/Views/BackupSetupView.swift b/Sources/BackupFeature/Views/BackupSetupView.swift index eeaad481..3e19d500 100644 --- a/Sources/BackupFeature/Views/BackupSetupView.swift +++ b/Sources/BackupFeature/Views/BackupSetupView.swift @@ -6,6 +6,7 @@ final class BackupSetupView: UIView { let subtitleLabel = UILabel() let stackView = UIStackView() + let sftpButton = BackupSwitcherButton() let iCloudButton = BackupSwitcherButton() let dropboxButton = BackupSwitcherButton() let googleDriveButton = BackupSwitcherButton() @@ -60,32 +61,37 @@ final class BackupSetupView: UIView { googleDriveButton.logoImageView.image = Asset.restoreDrive.image googleDriveButton.showChevron() + sftpButton.titleLabel.text = Localized.Backup.sftp + sftpButton.logoImageView.image = Asset.restoreSFTP.image + sftpButton.showChevron() + stackView.axis = .vertical stackView.addArrangedSubview(googleDriveButton) stackView.addArrangedSubview(iCloudButton) stackView.addArrangedSubview(dropboxButton) + stackView.addArrangedSubview(sftpButton) addSubview(titleLabel) addSubview(subtitleLabel) addSubview(stackView) - titleLabel.snp.makeConstraints { make in - make.top.equalToSuperview().offset(15) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-41) + titleLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(15) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-41) } - subtitleLabel.snp.makeConstraints { make in - make.top.equalTo(titleLabel.snp.bottom).offset(8) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-41) + subtitleLabel.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(8) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-41) } - stackView.snp.makeConstraints { make in - make.top.equalTo(subtitleLabel.snp.bottom).offset(28) - make.left.equalToSuperview() - make.right.equalToSuperview() - make.bottom.lessThanOrEqualToSuperview() + stackView.snp.makeConstraints { + $0.top.equalTo(subtitleLabel.snp.bottom).offset(28) + $0.left.equalToSuperview() + $0.right.equalToSuperview() + $0.bottom.lessThanOrEqualToSuperview() } } diff --git a/Sources/RestoreFeature/Controllers/RestoreListController.swift b/Sources/RestoreFeature/Controllers/RestoreListController.swift index 803e46e7..c73e94ed 100644 --- a/Sources/RestoreFeature/Controllers/RestoreListController.swift +++ b/Sources/RestoreFeature/Controllers/RestoreListController.swift @@ -1,6 +1,6 @@ import HUD -import Shared import UIKit +import Shared import Combine import DrawerFeature import DependencyInjection @@ -51,12 +51,12 @@ public final class RestoreListController: UIViewController { } private func setupBindings() { - viewModel.hud + viewModel.hudPublisher .receive(on: DispatchQueue.main) .sink { [hud] in hud.update(with: $0) } .store(in: &cancellables) - viewModel.didFetchBackup + viewModel.backupPublisher .receive(on: DispatchQueue.main) .sink { [unowned self] in coordinator.toRestore(using: ndf, with: $0, from: self) @@ -88,7 +88,7 @@ public final class RestoreListController: UIViewController { screenView.sftpButton .publisher(for: .touchUpInside) .sink { [unowned self] in - coordinator.toSFTP(using: ndf, from: self) + viewModel.didTapCloud(.sftp, from: self) }.store(in: &cancellables) } diff --git a/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift b/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift index a76b5c74..2183035b 100644 --- a/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift +++ b/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift @@ -6,33 +6,27 @@ import Presentation public protocol RestoreCoordinating { func toChats(from: UIViewController) func toSuccess(from: UIViewController) - func toSFTP(using: String, from: UIViewController) func toDrawer(_: UIViewController, from: UIViewController) func toPassphrase(from: UIViewController, _: @escaping StringClosure) func toRestore(using: String, with: RestoreSettings, from: UIViewController) - func toRestoreReplacing(using: String, with: RestoreSettings, from: UIViewController) } public struct RestoreCoordinator: RestoreCoordinating { var pushPresenter: Presenting = PushPresenter() var bottomPresenter: Presenting = BottomPresenter() var replacePresenter: Presenting = ReplacePresenter() - var replaceLastPresenter: Presenting = ReplacePresenter(mode: .replaceLast) var successFactory: () -> UIViewController var chatListFactory: () -> UIViewController - var sftpFactory: (String) -> UIViewController var restoreFactory: (String, RestoreSettings) -> UIViewController var passphraseFactory: (@escaping StringClosure) -> UIViewController public init( successFactory: @escaping () -> UIViewController, chatListFactory: @escaping () -> UIViewController, - sftpFactory: @escaping (String) -> UIViewController, restoreFactory: @escaping (String, RestoreSettings) -> UIViewController, passphraseFactory: @escaping (@escaping StringClosure) -> UIViewController ) { - self.sftpFactory = sftpFactory self.successFactory = successFactory self.restoreFactory = restoreFactory self.chatListFactory = chatListFactory @@ -50,15 +44,6 @@ public extension RestoreCoordinator { pushPresenter.present(screen, from: parent) } - func toRestoreReplacing( - using ndf: String, - with settings: RestoreSettings, - from parent: UIViewController - ) { - let screen = restoreFactory(ndf, settings) - replaceLastPresenter.present(screen, from: parent) - } - func toChats(from parent: UIViewController) { let screen = chatListFactory() replacePresenter.present(screen, from: parent) @@ -80,9 +65,4 @@ public extension RestoreCoordinator { let screen = passphraseFactory(completion) bottomPresenter.present(screen, from: parent) } - - func toSFTP(using ndf: String, from parent: UIViewController) { - let screen = sftpFactory(ndf) - pushPresenter.present(screen, from: parent) - } } diff --git a/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift index d0119b84..a4c2402e 100644 --- a/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift +++ b/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift @@ -6,20 +6,22 @@ import Combine import BackupFeature import DependencyInjection +import SFTPFeature import iCloudFeature import DropboxFeature import GoogleDriveFeature final class RestoreListViewModel { + @Dependency private var sftpService: SFTPService @Dependency private var icloudService: iCloudInterface @Dependency private var dropboxService: DropboxInterface @Dependency private var googleDriveService: GoogleDriveInterface - var hud: AnyPublisher<HUDStatus, Never> { + var hudPublisher: AnyPublisher<HUDStatus, Never> { hudSubject.eraseToAnyPublisher() } - var didFetchBackup: AnyPublisher<RestoreSettings, Never> { + var backupPublisher: AnyPublisher<RestoreSettings, Never> { backupSubject.eraseToAnyPublisher() } @@ -36,10 +38,36 @@ final class RestoreListViewModel { case .dropbox: didRequestDropboxAuthorization(from: parent) case .sftp: - break + didRequestSFTPAuthorization(from: parent) } } + private func didRequestSFTPAuthorization(from controller: UIViewController) { + let params = SFTPAuthorizationParams(controller, { [weak self] in + guard let self = self else { return } + controller.navigationController?.popViewController(animated: true) + + self.hudSubject.send(.on(nil)) + + self.sftpService.fetchMetadata{ result in + switch result { + case .success(let settings): + self.hudSubject.send(.none) + + if let settings = settings { + self.backupSubject.send(settings) + } else { + self.backupSubject.send(.init(cloudService: .sftp)) + } + case .failure(let error): + self.hudSubject.send(.error(.init(with: error))) + } + } + }) + + sftpService.authorizeFlow(params) + } + private func didRequestDriveAuthorization(from controller: UIViewController) { googleDriveService.authorize(presenting: controller) { authResult in switch authResult { diff --git a/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift deleted file mode 100644 index 7bf34b4c..00000000 --- a/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift +++ /dev/null @@ -1,81 +0,0 @@ -import HUD -import Models -import Combine -import Foundation -import SFTPFeature -import DependencyInjection - -struct RestoreSFTPViewState { - var host: String = "" - var username: String = "" - var password: String = "" - var isButtonEnabled: Bool = false -} - -final class RestoreSFTPViewModel { - @Dependency private var service: SFTPService - - var hudPublisher: AnyPublisher<HUDStatus, Never> { - hudSubject.eraseToAnyPublisher() - } - - var backupPublisher: AnyPublisher<RestoreSettings, Never> { - backupSubject.eraseToAnyPublisher() - } - - var statePublisher: AnyPublisher<RestoreSFTPViewState, Never> { - stateSubject.eraseToAnyPublisher() - } - - private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none) - private let backupSubject = PassthroughSubject<RestoreSettings, Never>() - private let stateSubject = CurrentValueSubject<RestoreSFTPViewState, Never>(.init()) - - func didEnterHost(_ string: String) { - stateSubject.value.host = string - validate() - } - - func didEnterUsername(_ string: String) { - stateSubject.value.username = string - validate() - } - - func didEnterPassword(_ string: String) { - stateSubject.value.password = string - validate() - } - - func didTapLogin() { - hudSubject.send(.on(nil)) - - let host = stateSubject.value.host - let username = stateSubject.value.username - let password = stateSubject.value.password - - let completion: SFTPFetchResult = { result in - switch result { - case .success(let backup): - self.hudSubject.send(.none) - - if let backup = backup { - self.backupSubject.send(backup) - } else { - self.backupSubject.send(.init(cloudService: .sftp)) - } - case .failure(let error): - self.hudSubject.send(.error(.init(with: error))) - } - } - - let authParams = SFTPAuthParams(host, username, password) - service.fetch((authParams, completion)) - } - - private func validate() { - stateSubject.value.isButtonEnabled = - !stateSubject.value.host.isEmpty && - !stateSubject.value.username.isEmpty && - !stateSubject.value.password.isEmpty - } -} diff --git a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift index ccb417df..5f1d4414 100644 --- a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift +++ b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift @@ -91,7 +91,7 @@ final class RestoreViewModel { private func downloadBackupForSFTP(_ backup: Backup) { do { - try sftpService.download(backup.id) + try sftpService.downloadBackup(backup.id) } catch { print(error.localizedDescription) } diff --git a/Sources/RestoreFeature/Views/RestoreSFTPView.swift b/Sources/RestoreFeature/Views/RestoreSFTPView.swift deleted file mode 100644 index de41e371..00000000 --- a/Sources/RestoreFeature/Views/RestoreSFTPView.swift +++ /dev/null @@ -1,76 +0,0 @@ -import UIKit -import Shared -import InputField - -final class RestoreSFTPView: UIView { - let titleLabel = UILabel() - let subtitleLabel = UILabel() - let hostField = OutlinedInputField() - let usernameField = OutlinedInputField() - let passwordField = OutlinedInputField() - let loginButton = CapsuleButton() - let stackView = UIStackView() - - init() { - super.init(frame: .zero) - backgroundColor = Asset.neutralWhite.color - - titleLabel.textColor = Asset.neutralDark.color - titleLabel.text = Localized.AccountRestore.Sftp.title - titleLabel.font = Fonts.Mulish.bold.font(size: 24.0) - - let paragraph = NSMutableParagraphStyle() - paragraph.alignment = .left - paragraph.lineHeightMultiple = 1.15 - - let attString = NSAttributedString( - string: Localized.AccountRestore.Sftp.subtitle, - attributes: [ - .foregroundColor: Asset.neutralBody.color, - .font: Fonts.Mulish.regular.font(size: 16.0) as Any, - .paragraphStyle: paragraph - ]) - - subtitleLabel.numberOfLines = 0 - subtitleLabel.attributedText = attString - - hostField.setup(title: Localized.AccountRestore.Sftp.host) - usernameField.setup(title: Localized.AccountRestore.Sftp.username) - passwordField.setup(title: Localized.AccountRestore.Sftp.password, sensitive: true) - - loginButton.set(style: .brandColored, title: Localized.AccountRestore.Sftp.login) - - stackView.spacing = 30 - stackView.axis = .vertical - stackView.distribution = .fillEqually - stackView.addArrangedSubview(hostField) - stackView.addArrangedSubview(usernameField) - stackView.addArrangedSubview(passwordField) - stackView.addArrangedSubview(loginButton) - - addSubview(titleLabel) - addSubview(subtitleLabel) - addSubview(stackView) - - titleLabel.snp.makeConstraints { - $0.top.equalTo(safeAreaLayoutGuide).offset(15) - $0.left.equalToSuperview().offset(38) - $0.right.equalToSuperview().offset(-41) - } - - subtitleLabel.snp.makeConstraints { - $0.top.equalTo(titleLabel.snp.bottom).offset(8) - $0.left.equalToSuperview().offset(38) - $0.right.equalToSuperview().offset(-41) - } - - stackView.snp.makeConstraints { - $0.top.equalTo(subtitleLabel.snp.bottom).offset(28) - $0.left.equalToSuperview().offset(38) - $0.right.equalToSuperview().offset(-38) - $0.bottom.lessThanOrEqualToSuperview() - } - } - - required init?(coder: NSCoder) { nil } -} diff --git a/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift b/Sources/SFTPFeature/SFTPController.swift similarity index 81% rename from Sources/RestoreFeature/Controllers/RestoreSFTPController.swift rename to Sources/SFTPFeature/SFTPController.swift index 024b2df4..1fe7f4c4 100644 --- a/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift +++ b/Sources/SFTPFeature/SFTPController.swift @@ -3,27 +3,26 @@ import UIKit import Combine import DependencyInjection -public final class RestoreSFTPController: UIViewController { +public final class SFTPController: UIViewController { @Dependency private var hud: HUDType - @Dependency private var coordinator: RestoreCoordinating - lazy private var screenView = RestoreSFTPView() + lazy private var screenView = SFTPView() - private let ndf: String - private let viewModel = RestoreSFTPViewModel() + private let completion: () -> Void + private let viewModel = SFTPViewModel() private var cancellables = Set<AnyCancellable>() - public override func loadView() { - view = screenView - } - - public init(_ ndf: String) { - self.ndf = ndf + public init(_ completion: @escaping () -> Void) { + self.completion = completion super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { nil } + public override func loadView() { + view = screenView + } + public override func viewDidLoad() { super.viewDidLoad() setupNavigationBar() @@ -47,11 +46,10 @@ public final class RestoreSFTPController: UIViewController { .sink { [hud] in hud.update(with: $0) } .store(in: &cancellables) - viewModel.backupPublisher + viewModel.authPublisher .receive(on: DispatchQueue.main) - .sink { [unowned self] in - coordinator.toRestoreReplacing(using: ndf, with: $0, from: self) - }.store(in: &cancellables) + .sink { [unowned self] in completion() } + .store(in: &cancellables) screenView.hostField .textPublisher diff --git a/Sources/SFTPFeature/SFTPService.swift b/Sources/SFTPFeature/SFTPService.swift index b581362e..aca3e200 100644 --- a/Sources/SFTPFeature/SFTPService.swift +++ b/Sources/SFTPFeature/SFTPService.swift @@ -1,19 +1,22 @@ +import UIKit import Shout import Models +import Combine import Keychain import Foundation +import Presentation import DependencyInjection -public typealias SFTPAuthParams = (String, String, String) public typealias SFTPFetchResult = (Result<RestoreSettings?, Error>) -> Void -public typealias SFTPFetchParams = (SFTPAuthParams, SFTPFetchResult) +public typealias SFTPAuthorizationParams = (UIViewController, () -> Void) public struct SFTPService { public var isAuthorized: () -> Bool - public var upload: (URL) throws -> Void - public var fetch: (SFTPFetchParams) -> Void - public var download: (String) throws -> Void - public var justAuthenticate: (SFTPAuthParams) throws -> Void + public var uploadBackup: (URL) throws -> Void + public var downloadBackup: (String) throws -> Void + public var fetchMetadata: (SFTPFetchResult) -> Void + public var authenticate: (String, String, String) throws -> Void + public var authorizeFlow: (SFTPAuthorizationParams) -> Void } public extension SFTPService { @@ -22,27 +25,29 @@ public extension SFTPService { print("^^^ Requested auth status on sftp service") return true }, - upload: { url in + uploadBackup: { url in print("^^^ Requested upload on sftp service") print("^^^ URL path: \(url.path)") }, - fetch: { (authParams, completion) in - print("^^^ Requested backup metadata on sftp service.") - print("^^^ Host: \(authParams.0)") - print("^^^ Username: \(authParams.1)") - print("^^^ Password: \(authParams.2)") - completion(.success(nil)) - }, - download: { path in + downloadBackup: { path in print("^^^ Requested backup download on sftp service.") print("^^^ Path: \(path)") }, - justAuthenticate: { host, username, password in - print("^^^ Requested to authenticate on sftp service") + fetchMetadata: { completion in + print("^^^ Requested backup metadata on sftp service.") + completion(.success(nil)) + }, + authenticate: { host, username, password in + print("^^^ Requested authentication on sftp service.") print("^^^ Host: \(host)") print("^^^ Username: \(username)") print("^^^ Password: \(password)") - }) + }, + authorizeFlow: { (_, completion) in + print("^^^ Requested authorizing flow on sftp service.") + completion() + } + ) static var live = SFTPService( isAuthorized: { @@ -51,11 +56,11 @@ public extension SFTPService { let host = try? keychain.get(key: .host), let username = try? keychain.get(key: .username) { return true - } else { - return false } + + return false }, - upload: { url in + uploadBackup: { url in let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling let host = try keychain.get(key: .host) let password = try keychain.get(key: .pwd) @@ -67,20 +72,30 @@ public extension SFTPService { try sftp.upload(localURL: url, remotePath: "backup/backup.xxm") }, - fetch: { (authParams, completion) in - let host = authParams.0 - let username = authParams.1 - let password = authParams.2 + downloadBackup: { path in + let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling + let host = try keychain.get(key: .host) + let password = try keychain.get(key: .pwd) + let username = try keychain.get(key: .username) - do { - let ssh = try SSH(host: host, port: 22) - try ssh.authenticate(username: username, password: password) - let sftp = try ssh.openSftp() + let ssh = try SSH(host: host!, port: 22) + try ssh.authenticate(username: username!, password: password!) + let sftp = try ssh.openSftp() + let temp = NSTemporaryDirectory() + try sftp.download(remotePath: path, localURL: URL(string: temp)!) + print(FileManager.default.fileExists(atPath: temp)) + }, + fetchMetadata: { completion in + do { let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling - try keychain.store(key: .host, value: host) - try keychain.store(key: .pwd, value: password) - try keychain.store(key: .username, value: username) + let host = try keychain.get(key: .host) + let password = try keychain.get(key: .pwd) + let username = try keychain.get(key: .username) + + let ssh = try SSH(host: host!, port: 22) + try ssh.authenticate(username: username!, password: password!) + let sftp = try ssh.openSftp() if let files = try? sftp.listFiles(in: "backup"), let backup = files.filter({ file in file.0 == "backup.xxm" }).first { @@ -101,25 +116,19 @@ public extension SFTPService { completion(.failure(error)) } }, - download: { path in - let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling - let host = try keychain.get(key: .host) - let password = try keychain.get(key: .pwd) - let username = try keychain.get(key: .username) - - let ssh = try SSH(host: host!, port: 22) - try ssh.authenticate(username: username!, password: password!) + authenticate: { host, username, password in + let ssh = try SSH(host: host, port: 22) + try ssh.authenticate(username: username, password: password) let sftp = try ssh.openSftp() - let temp = NSTemporaryDirectory() - try sftp.download(remotePath: path, localURL: URL(string: temp)!) - print(FileManager.default.fileExists(atPath: temp)) - }, - justAuthenticate: { host, username, password in let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling try keychain.store(key: .host, value: host) try keychain.store(key: .pwd, value: password) try keychain.store(key: .username, value: username) + }, + authorizeFlow: { controller, completion in + var pushPresenter: Presenting = PushPresenter() + pushPresenter.present(SFTPController(completion), from: controller) } ) } diff --git a/Sources/BackupFeature/Views/BackupSFTPView.swift b/Sources/SFTPFeature/SFTPView.swift similarity index 98% rename from Sources/BackupFeature/Views/BackupSFTPView.swift rename to Sources/SFTPFeature/SFTPView.swift index 3a88917a..3e62e4ca 100644 --- a/Sources/BackupFeature/Views/BackupSFTPView.swift +++ b/Sources/SFTPFeature/SFTPView.swift @@ -2,7 +2,7 @@ import UIKit import Shared import InputField -final class BackupSFTPView: UIView { +final class SFTPView: UIView { let titleLabel = UILabel() let subtitleLabel = UILabel() let hostField = OutlinedInputField() diff --git a/Sources/BackupFeature/ViewModels/BackupSFTPViewModel.swift b/Sources/SFTPFeature/SFTPViewModel.swift similarity index 63% rename from Sources/BackupFeature/ViewModels/BackupSFTPViewModel.swift rename to Sources/SFTPFeature/SFTPViewModel.swift index f9518a57..ea309cb6 100644 --- a/Sources/BackupFeature/ViewModels/BackupSFTPViewModel.swift +++ b/Sources/SFTPFeature/SFTPViewModel.swift @@ -1,35 +1,33 @@ import HUD -import Models import Combine import Foundation -import SFTPFeature import DependencyInjection -struct BackupSFTPViewState { +struct SFTPViewState { var host: String = "" var username: String = "" var password: String = "" var isButtonEnabled: Bool = false } -final class BackupSFTPViewModel { +final class SFTPViewModel { @Dependency private var service: SFTPService var hudPublisher: AnyPublisher<HUDStatus, Never> { hudSubject.eraseToAnyPublisher() } - var popPublisher: AnyPublisher<Void, Never> { - popSubject.eraseToAnyPublisher() + var statePublisher: AnyPublisher<SFTPViewState, Never> { + stateSubject.eraseToAnyPublisher() } - var statePublisher: AnyPublisher<BackupSFTPViewState, Never> { - stateSubject.eraseToAnyPublisher() + var authPublisher: AnyPublisher<Void, Never> { + authSubject.eraseToAnyPublisher() } - private let popSubject = PassthroughSubject<Void, Never>() + private let authSubject = PassthroughSubject<Void, Never>() private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none) - private let stateSubject = CurrentValueSubject<BackupSFTPViewState, Never>(.init()) + private let stateSubject = CurrentValueSubject<SFTPViewState, Never>(.init()) func didEnterHost(_ string: String) { stateSubject.value.host = string @@ -53,14 +51,15 @@ final class BackupSFTPViewModel { let username = stateSubject.value.username let password = stateSubject.value.password - let authParams = SFTPAuthParams(host, username, password) - - do { - try service.justAuthenticate(authParams) - hudSubject.send(.none) - popSubject.send(()) - } catch { - hudSubject.send(.error(.init(with: error))) + DispatchQueue.global().async { [weak self] in + guard let self = self else { return } + do { + try self.service.authenticate(host, username, password) + self.hudSubject.send(.none) + self.authSubject.send(()) + } catch { + self.hudSubject.send(.error(.init(with: error))) + } } } diff --git a/Sources/Shared/Resources/en.lproj/Localizable.strings b/Sources/Shared/Resources/en.lproj/Localizable.strings index 45568316..684962fc 100644 --- a/Sources/Shared/Resources/en.lproj/Localizable.strings +++ b/Sources/Shared/Resources/en.lproj/Localizable.strings @@ -834,7 +834,7 @@ "accountRestore.sftp.title" = "Login to your SFTP"; "accountRestore.sftp.subtitle" -= "Login to your server. Your credentials will be automatically and securley saved locally on your device."; += "Login to your server. Your credentials will be automatically and securely saved locally on your device."; "accountRestore.sftp.host" = "Host"; "accountRestore.sftp.username" -- GitLab