Skip to content
Snippets Groups Projects
Commit 8fa61fa0 authored by Bruno Muniz's avatar Bruno Muniz :apple:
Browse files

Finishing backup sftp

parent 081777fb
No related branches found
No related tags found
2 merge requests!54Releasing 1.1.4,!42Adding SFTP as a service to backup/restore
Showing
with 170 additions and 392 deletions
......@@ -216,6 +216,7 @@ let package = Package(
.target(
name: "SFTPFeature",
dependencies: [
"HUD",
"Shared",
"Keychain",
"InputField",
......
import UIKit
import BackgroundTasks
import XXModels
import Theme
import XXModels
import XXLogger
import Defaults
import Integration
......
......@@ -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)
......
......@@ -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
......
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)
}
}
......@@ -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)
}
}
......@@ -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
......
......@@ -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)
}
......
......@@ -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()
}
}
......
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)
}
......
......@@ -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)
}
}
......@@ -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 {
......
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
}
}
......@@ -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)
}
......
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 }
}
......@@ -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
......
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)
}
)
}
......@@ -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()
......
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)))
}
}
}
......
......@@ -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"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment