Skip to content
Snippets Groups Projects
SFTPService.swift 5.4 KiB
Newer Older
Bruno Muniz's avatar
Bruno Muniz committed
import UIKit
import Shout
Bruno Muniz's avatar
Bruno Muniz committed
import Socket
Bruno Muniz's avatar
Bruno Muniz committed
import Combine
import Keychain
import Foundation
Bruno Muniz's avatar
Bruno Muniz committed
import Presentation
import DependencyInjection
Bruno Muniz's avatar
Bruno Muniz committed
public typealias SFTPDownloadResult = (Result<Data, Error>) -> Void
Bruno Muniz's avatar
Bruno Muniz committed
public typealias SFTPUploadResult = (Result<Backup, Error>) -> Void
Bruno Muniz's avatar
Bruno Muniz committed
public typealias SFTPFetchResult = (Result<RestoreSettings?, Error>) -> Void
Bruno Muniz's avatar
Bruno Muniz committed
public typealias SFTPAuthorizationParams = (UIViewController, () -> Void)
Bruno Muniz's avatar
Bruno Muniz committed
public struct SFTPService {
    public var isAuthorized: () -> Bool
    public var fetchMetadata: (@escaping SFTPFetchResult) -> Void
    public var uploadBackup: SFTPServiceBackupUploader
Bruno Muniz's avatar
Bruno Muniz committed
    public var authorizeFlow: (SFTPAuthorizationParams) -> Void
Bruno Muniz's avatar
Bruno Muniz committed
    public var authenticate: (String, String, String) throws -> Void
    public var downloadBackup: SFTPServiceBackupDownloader
Bruno Muniz's avatar
Bruno Muniz committed
}

public extension SFTPService {
    static var mock = SFTPService(
        isAuthorized: {
            print("^^^ Requested auth status on sftp service")
            return true
        },
Bruno Muniz's avatar
Bruno Muniz committed
        fetchMetadata: { completion in
            print("^^^ Requested backup metadata on sftp service.")
            completion(.success(nil))
        },
        uploadBackup: .mock,
Bruno Muniz's avatar
Bruno Muniz committed
        authorizeFlow: { (_, completion) in
            print("^^^ Requested authorizing flow on sftp service.")
            completion()
        },
Bruno Muniz's avatar
Bruno Muniz committed
        authenticate: { host, username, password in
            print("^^^ Requested authentication on sftp service.")
            print("^^^ Host: \(host)")
            print("^^^ Username: \(username)")
            print("^^^ Password: \(password)")
Bruno Muniz's avatar
Bruno Muniz committed
        },
        downloadBackup: .mock
Bruno Muniz's avatar
Bruno Muniz committed
    )
Bruno Muniz's avatar
Bruno Muniz committed
    static var live = 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) {
                return true
            }
Bruno Muniz's avatar
Bruno Muniz committed

            return false
Bruno Muniz's avatar
Bruno Muniz committed
        },
Bruno Muniz's avatar
Bruno Muniz committed
        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))
Bruno Muniz's avatar
Bruno Muniz committed
                }
        uploadBackup: .live ,
Bruno Muniz's avatar
Bruno Muniz committed
        authorizeFlow: { controller, completion in
            var pushPresenter: Presenting = PushPresenter()
            pushPresenter.present(SFTPController(completion), from: controller)
Bruno Muniz's avatar
Bruno Muniz committed
        },
        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
            }
        },
        downloadBackup: .live
Bruno Muniz's avatar
Bruno Muniz committed
    )
}