zircles-ios/Zircles/CombineSynchronizer.swift

178 lines
6.3 KiB
Swift

//
// CombineSynchronizer.swift
// wallet
//
// Created by Francisco Gindre on 1/27/20.
// Copyright © 2020 Francisco Gindre. All rights reserved.
//
import Foundation
import Combine
import ZcashLightClientKit
class CombineSynchronizer {
var initializer: Initializer {
synchronizer.initializer
}
var synchronizer: SDKSynchronizer
var status: CurrentValueSubject<Status, Never>
var progress: CurrentValueSubject<Float,Never>
var syncBlockHeight: CurrentValueSubject<BlockHeight,Never>
var minedTransaction = PassthroughSubject<PendingTransactionEntity,Never>()
var balance: CurrentValueSubject<Double,Never>
var verifiedBalance: CurrentValueSubject<Double,Never>
var cancellables = [AnyCancellable]()
var error = PassthroughSubject<Error, Never>()
var receivedTransactions: Future<[ConfirmedTransactionEntity],Never> {
Future<[ConfirmedTransactionEntity], Never>() {
promise in
DispatchQueue.global().async {
[weak self] in
guard let self = self else {
promise(.success([]))
return
}
promise(.success(self.synchronizer.receivedTransactions))
}
}
}
var sentTransactions: Future<[ConfirmedTransactionEntity], Never> {
Future<[ConfirmedTransactionEntity], Never>() {
promise in
DispatchQueue.global().async {
[weak self] in
guard let self = self else {
promise(.success([]))
return
}
promise(.success(self.synchronizer.sentTransactions))
}
}
}
var pendingTransactions: Future<[PendingTransactionEntity], Never> {
Future<[PendingTransactionEntity], Never>(){
[weak self ] promise in
guard let self = self else {
promise(.success([]))
return
}
DispatchQueue.global().async {
promise(.success(self.synchronizer.pendingTransactions))
}
}
}
init(initializer: Initializer) throws {
self.synchronizer = try SDKSynchronizer(initializer: initializer)
self.status = CurrentValueSubject(.disconnected)
self.progress = CurrentValueSubject(0)
self.balance = CurrentValueSubject(0)
self.verifiedBalance = CurrentValueSubject(0)
self.syncBlockHeight = CurrentValueSubject(ZcashSDK.SAPLING_ACTIVATION_HEIGHT)
NotificationCenter.default.publisher(for: .synchronizerSynced).sink(receiveValue: { _ in
self.balance.send(initializer.getBalance().asHumanReadableZecBalance())
self.verifiedBalance.send(initializer.getVerifiedBalance().asHumanReadableZecBalance())
}).store(in: &cancellables)
NotificationCenter.default.publisher(for: .synchronizerStarted).sink { _ in
self.status.send(.syncing)
}.store(in: &cancellables)
NotificationCenter.default.publisher(for: .synchronizerProgressUpdated).receive(on: DispatchQueue.main).sink(receiveValue: { (progressNotification) in
guard let newProgress = progressNotification.userInfo?[SDKSynchronizer.NotificationKeys.progress] as? Float else { return }
self.progress.send(newProgress)
guard let blockHeight = progressNotification.userInfo?[SDKSynchronizer.NotificationKeys.blockHeight] as? BlockHeight else { return }
self.syncBlockHeight.send(blockHeight)
}).store(in: &cancellables)
NotificationCenter.default.publisher(for: .synchronizerMinedTransaction).sink(receiveValue: {minedNotification in
guard let minedTx = minedNotification.userInfo?[SDKSynchronizer.NotificationKeys.minedTransaction] as? PendingTransactionEntity else { return }
self.minedTransaction.send(minedTx)
}).store(in: &cancellables)
NotificationCenter.default.publisher(for: .synchronizerFailed).sink { (notification) in
guard let error = notification.userInfo?[SDKSynchronizer.NotificationKeys.error] as? Error else {
self.error.send(ZirclesEnvironment.WalletError.genericError(message: "An error ocurred, but we can't figure out what it is. Please check device logs for more details")
)
return
}
self.error.send(error)
}.store(in: &cancellables)
}
func start(retry: Bool = false){
do {
if retry {
stop()
}
try synchronizer.start(retry: retry)
} catch {
logger.error("error starting \(error)")
}
}
func stop() {
do {
try synchronizer.stop()
} catch {
logger.error("error stopping \(error)")
}
}
func cancel(pendingTransaction: PendingTransactionEntity) -> Bool {
synchronizer.cancelSpend(transaction: pendingTransaction)
}
deinit {
for c in cancellables {
c.cancel()
}
}
func send(with spendingKey: String, zatoshi: Int64, to recipientAddress: String, memo: String?,from account: Int) -> Future<PendingTransactionEntity,Error> {
Future<PendingTransactionEntity, Error>() {
promise in
self.synchronizer.sendToAddress(spendingKey: spendingKey, zatoshi: zatoshi, toAddress: recipientAddress, memo: memo, from: account) { (result) in
switch result {
case .failure(let error):
promise(.failure(error))
case .success(let pendingTx):
promise(.success(pendingTx))
}
}
}
}
func latestHeight() -> Future<BlockHeight,Error> {
Future<BlockHeight,Error>() { promise in
DispatchQueue.global().async { [weak self] in
guard let self = self else { return }
do {
promise(.success(try self.initializer.lightWalletService.latestBlockHeight()))
} catch {
promise(.failure(error))
}
}
}
}
}