From 16774fd2aa74a15eea6caf0be5875fb3d0baaf02 Mon Sep 17 00:00:00 2001
From: Bruno Muniz Azevedo Filho <bruno@elixxir.io>
Date: Fri, 15 Jul 2022 02:28:04 -0300
Subject: [PATCH] Refactor sftp service

---
 .../BackupFeature/Service/BackupService.swift |   4 +-
 .../ActionHandlers/SFTPAuthenticator.swift    |  54 ++++++++
 .../SFTPDownloader.swift}                     |  10 +-
 .../ActionHandlers/SFTPFetcher.swift          |  68 ++++++++++
 .../SFTPUploader.swift}                       |  11 +-
 Sources/SFTPFeature/SFTPService.swift         | 116 ++----------------
 Sources/SFTPFeature/SFTPViewModel.swift       |   7 +-
 7 files changed, 153 insertions(+), 117 deletions(-)
 create mode 100644 Sources/SFTPFeature/ActionHandlers/SFTPAuthenticator.swift
 rename Sources/SFTPFeature/{SFTPServiceBackupDownloader.swift => ActionHandlers/SFTPDownloader.swift} (87%)
 create mode 100644 Sources/SFTPFeature/ActionHandlers/SFTPFetcher.swift
 rename Sources/SFTPFeature/{SFTPServiceBackupUploader.swift => ActionHandlers/SFTPUploader.swift} (89%)

diff --git a/Sources/BackupFeature/Service/BackupService.swift b/Sources/BackupFeature/Service/BackupService.swift
index 5e97919c..626adba5 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 00000000..389cdd46
--- /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 4c63ec26..6a435df0 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 00000000..a27df80f
--- /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 2c6ecece..fee691d1 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 25d882d9..f1a90856 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 ea309cb6..dcf397a1 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 {
-- 
GitLab