From 9f3daf5b2ff5c7c879af32ac2fbba8fef31b793b Mon Sep 17 00:00:00 2001
From: Bruno Muniz Azevedo Filho <bruno@elixxir.io>
Date: Fri, 15 Jul 2022 02:02:36 -0300
Subject: [PATCH] Fixed issue w/ sftp by removing throttle

---
 .../BackupFeature/Service/BackupService.swift |   6 +-
 Sources/Integration/Client.swift              |   7 +-
 Sources/Integration/Session/Session.swift     |  11 +-
 .../ViewModels/RestoreViewModel.swift         |   4 +-
 Sources/SFTPFeature/SFTPService.swift         | 166 +++++-------------
 .../SFTPServiceBackupDownloader.swift         |  54 ++++++
 .../SFTPServiceBackupUploader.swift           |  66 +++++++
 7 files changed, 182 insertions(+), 132 deletions(-)
 create mode 100644 Sources/SFTPFeature/SFTPServiceBackupDownloader.swift
 create mode 100644 Sources/SFTPFeature/SFTPServiceBackupUploader.swift

diff --git a/Sources/BackupFeature/Service/BackupService.swift b/Sources/BackupFeature/Service/BackupService.swift
index ed3b5b53..5e97919c 100644
--- a/Sources/BackupFeature/Service/BackupService.swift
+++ b/Sources/BackupFeature/Service/BackupService.swift
@@ -277,7 +277,7 @@ extension BackupService {
             .appendingPathComponent(UUID().uuidString)
 
         do {
-            try data.write(to: url)
+            try data.write(to: url, options: .atomic)
         } catch {
             print("Couldn't write to temp: \(error.localizedDescription)")
             return
@@ -324,14 +324,14 @@ extension BackupService {
                 }
             }
         case .sftp:
-            sftpService.uploadBackup(url, {
+            sftpService.uploadBackup(url: url) {
                 switch $0 {
                 case .success(let backup):
                     self.settings.value.backups[.sftp] = backup
                 case .failure(let error):
                     print(error.localizedDescription)
                 }
-            })
+            }
         }
     }
 }
diff --git a/Sources/Integration/Client.swift b/Sources/Integration/Client.swift
index 7d53fb7e..2b488ef9 100644
--- a/Sources/Integration/Client.swift
+++ b/Sources/Integration/Client.swift
@@ -89,7 +89,12 @@ public class Client {
     //    }
 
     public func addJson(_ string: String) {
-        guard let backupManager = backupManager else { return }
+        guard let backupManager = backupManager else {
+            fatalError()
+        }
+
+        print("^^^ Set params: \(string) to backup")
+
         backupManager.addJson(string)
     }
 
diff --git a/Sources/Integration/Session/Session.swift b/Sources/Integration/Session/Session.swift
index 8a1c2040..7c428f12 100644
--- a/Sources/Integration/Session/Session.swift
+++ b/Sources/Integration/Session/Session.swift
@@ -303,6 +303,11 @@ public final class Session: SessionType {
         ).jsonFormat
 
         client.addJson(params)
+
+        guard username!.isEmpty == false else {
+            fatalError("Tried to build a backup with my username but an empty string was set to it")
+        }
+
         backupService.performBackupIfAutomaticIsEnabled()
     }
 
@@ -328,7 +333,6 @@ public final class Session: SessionType {
             .store(in: &cancellables)
 
         client.backup
-            .throttle(for: .seconds(5), scheduler: DispatchQueue.main, latest: true)
             .sink { [unowned self] in backupService.updateBackup(data: $0) }
             .store(in: &cancellables)
 
@@ -349,7 +353,6 @@ public final class Session: SessionType {
                 if $0 == true {
                     guard let passphrase = backupService.passphrase else {
                         client.resumeBackup()
-                        updateFactsOnBackup()
                         return
                     }
 
@@ -363,10 +366,6 @@ public final class Session: SessionType {
             }
             .store(in: &cancellables)
 
-        networkMonitor.statusPublisher
-            .sink { print($0) }
-            .store(in: &cancellables)
-
         client.messages
             .sink { [unowned self] in
                 if var contact = try? dbManager.fetchContacts(.init(id: [$0.senderId])).first {
diff --git a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
index 09147154..bea37e51 100644
--- a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
+++ b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
@@ -90,7 +90,7 @@ final class RestoreViewModel {
     }
 
     private func downloadBackupForSFTP(_ backup: Backup) {
-        sftpService.downloadBackup(backup.id, { [weak self] in
+        sftpService.downloadBackup(path: backup.id) { [weak self] in
             guard let self = self else { return }
             self.stepRelay.send(.downloading(backup.size, backup.size))
 
@@ -100,7 +100,7 @@ final class RestoreViewModel {
             case .failure(let error):
                 self.stepRelay.send(.failDownload(error))
             }
-        })
+        }
     }
 
     private func downloadBackupForDropbox(_ backup: Backup) {
diff --git a/Sources/SFTPFeature/SFTPService.swift b/Sources/SFTPFeature/SFTPService.swift
index b040668b..25d882d9 100644
--- a/Sources/SFTPFeature/SFTPService.swift
+++ b/Sources/SFTPFeature/SFTPService.swift
@@ -15,11 +15,11 @@ public typealias SFTPAuthorizationParams = (UIViewController, () -> Void)
 
 public struct SFTPService {
     public var isAuthorized: () -> Bool
-    public var fetchMetadata: (SFTPFetchResult) -> Void
-    public var uploadBackup: (URL, SFTPUploadResult) -> Void
+    public var fetchMetadata: (@escaping SFTPFetchResult) -> Void
+    public var uploadBackup: SFTPServiceBackupUploader
     public var authorizeFlow: (SFTPAuthorizationParams) -> Void
     public var authenticate: (String, String, String) throws -> Void
-    public var downloadBackup: (String, SFTPDownloadResult) -> Void
+    public var downloadBackup: SFTPServiceBackupDownloader
 }
 
 public extension SFTPService {
@@ -32,10 +32,7 @@ public extension SFTPService {
             print("^^^ Requested backup metadata on sftp service.")
             completion(.success(nil))
         },
-        uploadBackup: { url, completion in
-            print("^^^ Requested upload on sftp service")
-            print("^^^ URL path: \(url.path)")
-        },
+        uploadBackup: .mock,
         authorizeFlow: { (_, completion) in
             print("^^^ Requested authorizing flow on sftp service.")
             completion()
@@ -46,10 +43,7 @@ public extension SFTPService {
             print("^^^ Username: \(username)")
             print("^^^ Password: \(password)")
         },
-        downloadBackup: { path, completion in
-            print("^^^ Requested backup download on sftp service.")
-            print("^^^ Path: \(path)")
-        }
+        downloadBackup: .mock
     )
 
     static var live = SFTPService(
@@ -64,89 +58,51 @@ public extension SFTPService {
             return false
         },
         fetchMetadata: { completion in
-            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))
-            }
-        },
-        uploadBackup: { url, completion in
-            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()
-
-                let data = try Data(contentsOf: url)
-
-                if (try? sftp.listFiles(in: "backup")) == nil {
-                    try sftp.createDirectory("backup")
-                }
+            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
+                    }
 
-                try sftp.upload(data: data, remotePath: "backup/backup.xxm")
+                    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(.success(.init(
-                    id: "backup/backup.xxm",
-                    date: Date(),
-                    size: Float(data.count)
-                )))
-            } 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))
                 }
-
-                completion(.failure(error))
             }
         },
+        uploadBackup: .live ,
         authorizeFlow: { controller, completion in
             var pushPresenter: Presenting = PushPresenter()
             pushPresenter.present(SFTPController(completion), from: controller)
@@ -182,36 +138,6 @@ public extension SFTPService {
                 throw error
             }
         },
-        downloadBackup: { path, completion in
-            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()
-
-                let localURL = FileManager.default
-                    .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
-                    .appendingPathComponent("sftp")
-
-                try sftp.download(remotePath: path, localURL: localURL)
-
-                let data = try Data(contentsOf: localURL)
-                completion(.success(data))
-            } catch {
-                completion(.failure(error))
-
-                if var error = error as? SSHError {
-                    print(error.kind)
-                    print(error.message)
-                    print(error.description)
-                } else {
-                    print(error.localizedDescription)
-                }
-            }
-        }
+        downloadBackup: .live
     )
 }
diff --git a/Sources/SFTPFeature/SFTPServiceBackupDownloader.swift b/Sources/SFTPFeature/SFTPServiceBackupDownloader.swift
new file mode 100644
index 00000000..4c63ec26
--- /dev/null
+++ b/Sources/SFTPFeature/SFTPServiceBackupDownloader.swift
@@ -0,0 +1,54 @@
+import Shout
+import Socket
+import Keychain
+import Foundation
+import DependencyInjection
+
+public struct SFTPServiceBackupDownloader {
+    public var download: (String, @escaping SFTPDownloadResult) -> Void
+
+    public func callAsFunction(path: String, completion: @escaping SFTPDownloadResult) {
+        download(path, completion)
+    }
+}
+
+extension SFTPServiceBackupDownloader {
+    static let mock = SFTPServiceBackupDownloader { path, _ in
+        print("^^^ Requested backup download on sftp service.")
+        print("^^^ Path: \(path)")
+    }
+
+    static let live = SFTPServiceBackupDownloader { path, 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()
+
+                let localURL = FileManager.default
+                    .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
+                    .appendingPathComponent("sftp")
+
+                try sftp.download(remotePath: path, localURL: localURL)
+
+                let data = try Data(contentsOf: localURL)
+                completion(.success(data))
+            } catch {
+                completion(.failure(error))
+
+                if var error = error as? SSHError {
+                    print(error.kind)
+                    print(error.message)
+                    print(error.description)
+                } else {
+                    print(error.localizedDescription)
+                }
+            }
+        }
+    }
+}
diff --git a/Sources/SFTPFeature/SFTPServiceBackupUploader.swift b/Sources/SFTPFeature/SFTPServiceBackupUploader.swift
new file mode 100644
index 00000000..2c6ecece
--- /dev/null
+++ b/Sources/SFTPFeature/SFTPServiceBackupUploader.swift
@@ -0,0 +1,66 @@
+import Shout
+import Socket
+import Keychain
+import Foundation
+import DependencyInjection
+
+public struct SFTPServiceBackupUploader {
+    public var upload: (URL, @escaping SFTPUploadResult) -> Void
+
+    public func callAsFunction(url: URL, completion: @escaping SFTPUploadResult) {
+        upload(url, completion)
+    }
+}
+
+extension SFTPServiceBackupUploader {
+    static let mock = SFTPServiceBackupUploader(
+        upload: { url, _ in
+            print("^^^ Requested upload on sftp service")
+            print("^^^ URL path: \(url.path)")
+        }
+    )
+
+    static let live = SFTPServiceBackupUploader { url, 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()
+
+                let data = try Data(contentsOf: url)
+
+                if (try? sftp.listFiles(in: "backup")) == nil {
+                    try sftp.createDirectory("backup")
+                }
+
+                try sftp.upload(data: data, remotePath: "backup/backup.xxm")
+
+                completion(.success(.init(
+                    id: "backup/backup.xxm",
+                    date: Date(),
+                    size: Float(data.count)
+                )))
+            } 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))
+            }
+        }
+    }
+}
-- 
GitLab