diff --git a/Sources/BackupFeature/Service/BackupService.swift b/Sources/BackupFeature/Service/BackupService.swift index 5e97919c7a36a7ebd6ae9cef368e6d6d59420ef1..626adba515d28c1a9969b067da742d07e94c8da5 100644 --- a/Sources/BackupFeature/Service/BackupService.swift +++ b/Sources/BackupFeature/Service/BackupService.swift @@ -216,7 +216,7 @@ extension BackupService { } if sftpService.isAuthorized() { - sftpService.fetchMetadata({ [weak settings] in + sftpService.fetchMetadata { [weak settings] in guard let settings = settings else { return } guard let metadata = try? $0.get()?.backup else { @@ -229,7 +229,7 @@ extension BackupService { date: metadata.date, size: metadata.size ) - }) + } } if dropboxService.isAuthorized() { diff --git a/Sources/SFTPFeature/ActionHandlers/SFTPAuthenticator.swift b/Sources/SFTPFeature/ActionHandlers/SFTPAuthenticator.swift new file mode 100644 index 0000000000000000000000000000000000000000..389cdd46fc0c0136247e09f3dbd900ec265a7858 --- /dev/null +++ b/Sources/SFTPFeature/ActionHandlers/SFTPAuthenticator.swift @@ -0,0 +1,54 @@ +import Shout +import Socket +import Keychain +import Foundation +import DependencyInjection + +public struct SFTPAuthenticator { + public var authenticate: (String, String, String) throws -> Void + + public func callAsFunction(host: String, username: String, password: String) throws { + try authenticate(host, username, password) + } +} + +extension SFTPAuthenticator { + static let mock = SFTPAuthenticator { host, username, password in + print("^^^ Requested authentication on sftp service.") + print("^^^ Host: \(host)") + print("^^^ Username: \(username)") + print("^^^ Password: \(password)") + } + + static let live = SFTPAuthenticator { host, username, password in + do { + try SSH.connect( + host: host, + port: 22, + username: username, + authMethod: SSHPassword(password)) { ssh in + _ = try ssh.openSftp() + + 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) + } + } catch { + if let error = error as? SSHError { + print(error.kind) + print(error.message) + print(error.description) + } else if let error = error as? Socket.Error { + print(error.errorCode) + print(error.description) + print(error.errorReason) + print(error.localizedDescription) + } else { + print(error.localizedDescription) + } + + throw error + } + } +} diff --git a/Sources/SFTPFeature/SFTPServiceBackupDownloader.swift b/Sources/SFTPFeature/ActionHandlers/SFTPDownloader.swift similarity index 87% rename from Sources/SFTPFeature/SFTPServiceBackupDownloader.swift rename to Sources/SFTPFeature/ActionHandlers/SFTPDownloader.swift index 4c63ec26b8de7d95c46616100e1c73712cec0933..6a435df0051a2755f2fb73522f7be92a24c4dd77 100644 --- a/Sources/SFTPFeature/SFTPServiceBackupDownloader.swift +++ b/Sources/SFTPFeature/ActionHandlers/SFTPDownloader.swift @@ -4,7 +4,9 @@ import Keychain import Foundation import DependencyInjection -public struct SFTPServiceBackupDownloader { +public typealias SFTPDownloadResult = (Result<Data, Error>) -> Void + +public struct SFTPDownloader { public var download: (String, @escaping SFTPDownloadResult) -> Void public func callAsFunction(path: String, completion: @escaping SFTPDownloadResult) { @@ -12,13 +14,13 @@ public struct SFTPServiceBackupDownloader { } } -extension SFTPServiceBackupDownloader { - static let mock = SFTPServiceBackupDownloader { path, _ in +extension SFTPDownloader { + static let mock = SFTPDownloader { path, _ in print("^^^ Requested backup download on sftp service.") print("^^^ Path: \(path)") } - static let live = SFTPServiceBackupDownloader { path, completion in + static let live = SFTPDownloader { path, completion in DispatchQueue.global().async { do { let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling diff --git a/Sources/SFTPFeature/ActionHandlers/SFTPFetcher.swift b/Sources/SFTPFeature/ActionHandlers/SFTPFetcher.swift new file mode 100644 index 0000000000000000000000000000000000000000..a27df80ffe8e9be6f41f09185e9962351c44cff9 --- /dev/null +++ b/Sources/SFTPFeature/ActionHandlers/SFTPFetcher.swift @@ -0,0 +1,68 @@ +import Shout +import Socket +import Models +import Keychain +import Foundation +import DependencyInjection + +public typealias SFTPFetchResult = (Result<RestoreSettings?, Error>) -> Void + +public struct SFTPFetcher { + public var fetch: (@escaping SFTPFetchResult) -> Void + + public func callAsFunction(completion: @escaping SFTPFetchResult) { + fetch(completion) + } +} + +extension SFTPFetcher { + static let mock = SFTPFetcher { _ in + print("^^^ Requested backup metadata on sftp service.") + } + + static let live = SFTPFetcher { completion in + DispatchQueue.global().async { + do { + 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!) + let sftp = try ssh.openSftp() + + if let files = try? sftp.listFiles(in: "backup"), + let backup = files.filter({ file in file.0 == "backup.xxm" }).first { + completion(.success(.init( + backup: .init( + id: "backup/backup.xxm", + date: backup.value.lastModified, + size: Float(backup.value.size) + ), + cloudService: .sftp + ))) + + return + } + + completion(.success(nil)) + } catch { + if let error = error as? SSHError { + print(error.kind) + print(error.message) + print(error.description) + } else if let error = error as? Socket.Error { + print(error.errorCode) + print(error.description) + print(error.errorReason) + print(error.localizedDescription) + } else { + print(error.localizedDescription) + } + + completion(.failure(error)) + } + } + } +} diff --git a/Sources/SFTPFeature/SFTPServiceBackupUploader.swift b/Sources/SFTPFeature/ActionHandlers/SFTPUploader.swift similarity index 89% rename from Sources/SFTPFeature/SFTPServiceBackupUploader.swift rename to Sources/SFTPFeature/ActionHandlers/SFTPUploader.swift index 2c6ecece87ef2ab7223f860e90b39ba81df2a6ed..fee691d1b7e1669226fecfad6ecc37c97e64f9c5 100644 --- a/Sources/SFTPFeature/SFTPServiceBackupUploader.swift +++ b/Sources/SFTPFeature/ActionHandlers/SFTPUploader.swift @@ -1,10 +1,13 @@ import Shout import Socket +import Models import Keychain import Foundation import DependencyInjection -public struct SFTPServiceBackupUploader { +public typealias SFTPUploadResult = (Result<Backup, Error>) -> Void + +public struct SFTPUploader { public var upload: (URL, @escaping SFTPUploadResult) -> Void public func callAsFunction(url: URL, completion: @escaping SFTPUploadResult) { @@ -12,15 +15,15 @@ public struct SFTPServiceBackupUploader { } } -extension SFTPServiceBackupUploader { - static let mock = SFTPServiceBackupUploader( +extension SFTPUploader { + static let mock = SFTPUploader( upload: { url, _ in print("^^^ Requested upload on sftp service") print("^^^ URL path: \(url.path)") } ) - static let live = SFTPServiceBackupUploader { url, completion in + static let live = SFTPUploader { url, completion in DispatchQueue.global().async { do { let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling diff --git a/Sources/SFTPFeature/SFTPService.swift b/Sources/SFTPFeature/SFTPService.swift index 25d882d9e529de1fc563cb49acab755e888feca9..f1a908564df810ed2aeaa1b4af358b367253f84b 100644 --- a/Sources/SFTPFeature/SFTPService.swift +++ b/Sources/SFTPFeature/SFTPService.swift @@ -1,48 +1,26 @@ import UIKit -import Shout -import Socket -import Models -import Combine import Keychain -import Foundation import Presentation import DependencyInjection -public typealias SFTPDownloadResult = (Result<Data, Error>) -> Void -public typealias SFTPUploadResult = (Result<Backup, Error>) -> Void -public typealias SFTPFetchResult = (Result<RestoreSettings?, Error>) -> Void public typealias SFTPAuthorizationParams = (UIViewController, () -> Void) public struct SFTPService { public var isAuthorized: () -> Bool - public var fetchMetadata: (@escaping SFTPFetchResult) -> Void - public var uploadBackup: SFTPServiceBackupUploader + public var fetchMetadata: SFTPFetcher + public var uploadBackup: SFTPUploader public var authorizeFlow: (SFTPAuthorizationParams) -> Void - public var authenticate: (String, String, String) throws -> Void - public var downloadBackup: SFTPServiceBackupDownloader + public var authenticate: SFTPAuthenticator + public var downloadBackup: SFTPDownloader } public extension SFTPService { static var mock = SFTPService( - isAuthorized: { - print("^^^ Requested auth status on sftp service") - return true - }, - fetchMetadata: { completion in - print("^^^ Requested backup metadata on sftp service.") - completion(.success(nil)) - }, + isAuthorized: { true }, + fetchMetadata: .mock, uploadBackup: .mock, - authorizeFlow: { (_, completion) in - print("^^^ Requested authorizing flow on sftp service.") - completion() - }, - authenticate: { host, username, password in - print("^^^ Requested authentication on sftp service.") - print("^^^ Host: \(host)") - print("^^^ Username: \(username)") - print("^^^ Password: \(password)") - }, + authorizeFlow: { (_, completion) in completion() }, + authenticate: .mock, downloadBackup: .mock ) @@ -57,87 +35,13 @@ public extension SFTPService { return false }, - fetchMetadata: { completion in - DispatchQueue.global().async { - do { - 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!) - let sftp = try ssh.openSftp() - - if let files = try? sftp.listFiles(in: "backup"), - let backup = files.filter({ file in file.0 == "backup.xxm" }).first { - completion(.success(.init( - backup: .init( - id: "backup/backup.xxm", - date: backup.value.lastModified, - size: Float(backup.value.size) - ), - cloudService: .sftp - ))) - - return - } - - completion(.success(nil)) - } catch { - if let error = error as? SSHError { - print(error.kind) - print(error.message) - print(error.description) - } else if let error = error as? Socket.Error { - print(error.errorCode) - print(error.description) - print(error.errorReason) - print(error.localizedDescription) - } else { - print(error.localizedDescription) - } - - completion(.failure(error)) - } - } - }, + fetchMetadata: .live, uploadBackup: .live , authorizeFlow: { controller, completion in var pushPresenter: Presenting = PushPresenter() pushPresenter.present(SFTPController(completion), from: controller) }, - authenticate: { host, username, password in - do { - try SSH.connect( - host: host, - port: 22, - username: username, - authMethod: SSHPassword(password)) { ssh in - _ = try ssh.openSftp() - - 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) - } - } catch { - if let error = error as? SSHError { - print(error.kind) - print(error.message) - print(error.description) - } else if let error = error as? Socket.Error { - print(error.errorCode) - print(error.description) - print(error.errorReason) - print(error.localizedDescription) - } else { - print(error.localizedDescription) - } - - throw error - } - }, + authenticate: .live, downloadBackup: .live ) } diff --git a/Sources/SFTPFeature/SFTPViewModel.swift b/Sources/SFTPFeature/SFTPViewModel.swift index ea309cb68109d2daf1f5b0e908ea9ce005115bca..dcf397a1da1c474681001b87df0d148ae26b2706 100644 --- a/Sources/SFTPFeature/SFTPViewModel.swift +++ b/Sources/SFTPFeature/SFTPViewModel.swift @@ -54,7 +54,12 @@ final class SFTPViewModel { DispatchQueue.global().async { [weak self] in guard let self = self else { return } do { - try self.service.authenticate(host, username, password) + try self.service.authenticate( + host: host, + username: username, + password: password + ) + self.hudSubject.send(.none) self.authSubject.send(()) } catch {