-
Dariusz Rybicki authored
Restore facts from UD
Dariusz Rybicki authoredRestore facts from UD
RestoreFeature.swift 4.30 KiB
import AppCore
import Combine
import ComposableArchitecture
import Foundation
import XCTestDynamicOverlay
import XXMessengerClient
import XXModels
public struct RestoreState: Equatable {
public enum Field: String, Hashable {
case passphrase
}
public struct File: Equatable {
public init(name: String, data: Data) {
self.name = name
self.data = data
}
public var name: String
public var data: Data
}
public init(
file: File? = nil,
fileImportFailure: String? = nil,
restoreFailure: String? = nil,
focusedField: Field? = nil,
isImportingFile: Bool = false,
passphrase: String = "",
isRestoring: Bool = false
) {
self.file = file
self.fileImportFailure = fileImportFailure
self.restoreFailure = restoreFailure
self.focusedField = focusedField
self.isImportingFile = isImportingFile
self.passphrase = passphrase
self.isRestoring = isRestoring
}
public var file: File?
public var fileImportFailure: String?
public var restoreFailure: String?
@BindableState public var focusedField: Field?
@BindableState public var isImportingFile: Bool
@BindableState public var passphrase: String
@BindableState public var isRestoring: Bool
}
public enum RestoreAction: Equatable, BindableAction {
case importFileTapped
case fileImport(Result<URL, NSError>)
case restoreTapped
case finished
case failed(NSError)
case binding(BindingAction<RestoreState>)
}
public struct RestoreEnvironment {
public init(
messenger: Messenger,
db: DBManagerGetDB,
loadData: URLDataLoader,
now: @escaping () -> Date,
mainQueue: AnySchedulerOf<DispatchQueue>,
bgQueue: AnySchedulerOf<DispatchQueue>
) {
self.messenger = messenger
self.db = db
self.loadData = loadData
self.now = now
self.mainQueue = mainQueue
self.bgQueue = bgQueue
}
public var messenger: Messenger
public var db: DBManagerGetDB
public var loadData: URLDataLoader
public var now: () -> Date
public var mainQueue: AnySchedulerOf<DispatchQueue>
public var bgQueue: AnySchedulerOf<DispatchQueue>
}
extension RestoreEnvironment {
public static let unimplemented = RestoreEnvironment(
messenger: .unimplemented,
db: .unimplemented,
loadData: .unimplemented,
now: XCTUnimplemented("\(Self.self).now"),
mainQueue: .unimplemented,
bgQueue: .unimplemented
)
}
public let restoreReducer = Reducer<RestoreState, RestoreAction, RestoreEnvironment>
{ state, action, env in
switch action {
case .importFileTapped:
state.isImportingFile = true
state.fileImportFailure = nil
return .none
case .fileImport(.success(let url)):
state.isImportingFile = false
do {
state.file = .init(
name: url.lastPathComponent,
data: try env.loadData(url)
)
state.fileImportFailure = nil
} catch {
state.file = nil
state.fileImportFailure = error.localizedDescription
}
return .none
case .fileImport(.failure(let error)):
state.isImportingFile = false
state.file = nil
state.fileImportFailure = error.localizedDescription
return .none
case .restoreTapped:
guard let backupData = state.file?.data, backupData.count > 0 else { return .none }
let backupPassphrase = state.passphrase
state.isRestoring = true
state.restoreFailure = nil
return Effect.result {
do {
let result = try env.messenger.restoreBackup(
backupData: backupData,
backupPassphrase: backupPassphrase
)
let facts = try env.messenger.ud.tryGet().getFacts()
try env.db().saveContact(Contact(
id: try env.messenger.e2e.tryGet().getContact().getId(),
username: result.restoredParams.username,
email: facts.get(.email)?.value,
phone: facts.get(.phone)?.value,
createdAt: env.now()
))
return .success(.finished)
} catch {
try? env.messenger.destroy()
return .success(.failed(error as NSError))
}
}
.subscribe(on: env.bgQueue)
.receive(on: env.mainQueue)
.eraseToEffect()
case .finished:
state.isRestoring = false
return .none
case .failed(let error):
state.isRestoring = false
state.restoreFailure = error.localizedDescription
return .none
case .binding(_):
return .none
}
}
.binding()