Replace Status for SyncStatus

This commit is contained in:
Francisco Gindre 2021-06-14 20:38:05 -03:00
parent a07fdc52dc
commit 558dbeb1ac
3 changed files with 213 additions and 53 deletions

View File

@ -90,8 +90,12 @@ struct BlockProgress: BlockProgressReporting {
var progressHeight: BlockHeight var progressHeight: BlockHeight
} }
extension Notification.Name {
static let connectionStatusChanged = Notification.Name("LightWalletServiceConnectivityStatusChanged")
}
public class LightWalletGRPCService { public class LightWalletGRPCService {
var queue: DispatchQueue var queue: DispatchQueue
let channel: Channel let channel: Channel
let connectionDelegate: ConnectionStatusManager let connectionDelegate: ConnectionStatusManager
@ -488,5 +492,12 @@ extension LightWalletServiceError {
class ConnectionStatusManager: ConnectivityStateDelegate { class ConnectionStatusManager: ConnectivityStateDelegate {
func connectivityStateDidChange(from oldState: ConnectivityState, to newState: ConnectivityState) { func connectivityStateDidChange(from oldState: ConnectivityState, to newState: ConnectivityState) {
LoggerProxy.event("Connection Changed from \(oldState) to \(newState)") LoggerProxy.event("Connection Changed from \(oldState) to \(newState)")
NotificationCenter.default.post(
name: .blockProcessorConnectivityStateChanged,
object: self,
userInfo: [
CompactBlockProcessorNotificationKey.currentConnectivityStatus : newState,
CompactBlockProcessorNotificationKey.previousConnectivityStatus : oldState
])
} }
} }

View File

@ -48,6 +48,32 @@ extension ShieldFundsError: LocalizedError {
} }
} }
/**
Represent the connection state to the lightwalletd server
*/
public enum ConnectionState {
/**
not in use
*/
case idle
/**
there's a connection being attempted from a non error state
*/
case connecting
/**
connection is established, ready to use or in use
*/
case online
/**
the connection is being re-established after losing it temporarily
*/
case reconnecting
/**
the connection has been closed
*/
case shutdown
}
/** /**
Primary interface for interacting with the SDK. Defines the contract that specific Primary interface for interacting with the SDK. Defines the contract that specific
@ -57,17 +83,9 @@ implementations like SdkSynchronizer fulfill.
public protocol Synchronizer { public protocol Synchronizer {
/** /**
Value representing the Status of this Synchronizer. As the status changes, a new Value representing the Status of this Synchronizer. As the status changes, it will be also notified
value will be emitted by KVO
*/ */
var status: Status { get } var status: SyncStatus { get }
/**
A flow of progress values, typically corresponding to this Synchronizer downloading blocks.
Typically, any non-zero value below 1.0 indicates that progress indicators can be shown and
a value of 1.0 signals that progress is complete and any progress indicators can be hidden. KVO Compliant
*/
var progress: Float { get }
/** /**
prepares this initializer to operate. Initializes the internal state with the given Extended Viewing Keys and a wallet birthday found in the initializer object prepares this initializer to operate. Initializes the internal state with the given Extended Viewing Keys and a wallet birthday found in the initializer object
@ -215,73 +233,65 @@ public protocol Synchronizer {
func rewind(_ policy: RewindPolicy) throws func rewind(_ policy: RewindPolicy) throws
} }
/** public enum SyncStatus: Equatable {
The Status of the synchronizer
*/
public enum Status {
/** /**
Indicates that this Synchronizer is actively preparing to start, which usually involves Indicates that this Synchronizer is actively preparing to start, which usually involves
setting up database tables, migrations or taking other maintenance steps that need to setting up database tables, migrations or taking other maintenance steps that need to
occur after an upgrade. occur after an upgrade.
*/ */
case unprepared case unprepared
/**
Indicates that [stop] has been called on this Synchronizer and it will no longer be used.
*/
case stopped
/**
Indicates that this Synchronizer is disconnected from its lightwalletd server.
When set, a UI element may want to turn red.
*/
case disconnected
/** /**
Indicates that this Synchronizer is actively downloading new blocks from the server. Indicates that this Synchronizer is actively downloading new blocks from the server.
*/ */
case downloading case downloading(_ status: BlockProgressReporting)
/** /**
Indicates that this Synchronizer is actively validating new blocks that were downloaded Indicates that this Synchronizer is actively validating new blocks that were downloaded
from the server. Blocks need to be verified before they are scanned. This confirms that from the server. Blocks need to be verified before they are scanned. This confirms that
each block is chain-sequential, thereby detecting missing blocks and reorgs. each block is chain-sequential, thereby detecting missing blocks and reorgs.
*/ */
case validating case validating
/** /**
Indicates that this Synchronizer is actively scanning new valid blocks that were downloaded Indicates that this Synchronizer is actively scanning new valid blocks that were downloaded
from the server. from the server.
*/ */
case scanning case scanning(_ progress: BlockProgressReporting)
/** /**
Indicates that this Synchronizer is actively enhancing newly scanned blocks with Indicates that this Synchronizer is actively enhancing newly scanned blocks with
additional transaction details, fetched from the server. additional transaction details, fetched from the server.
*/ */
case enhancing case enhancing(_ progress: EnhancementProgress)
/** /**
fetches the transparent balance and stores it locally fetches the transparent balance and stores it locally
*/ */
case fetching case fetching
/** /**
Indicates that this Synchronizer is fully up to date and ready for all wallet functions. Indicates that this Synchronizer is fully up to date and ready for all wallet functions.
When set, a UI element may want to turn green. When set, a UI element may want to turn green.
*/ */
case synced case synced
/**
Indicates that [stop] has been called on this Synchronizer and it will no longer be used.
*/
case stopped
/**
Indicates that this Synchronizer is disconnected from its lightwalletd server.
When set, a UI element may want to turn red.
*/
case disconnected
case error(_ error: Error)
public var isSyncing: Bool { public var isSyncing: Bool {
switch self { switch self {
case .disconnected, .synced, .synced, .unprepared: case .downloading, .validating, .scanning, .enhancing, .fetching:
return false
default:
return true return true
default:
return false
} }
} }
} }
/** /**
Kind of transactions handled by a Synchronizer Kind of transactions handled by a Synchronizer
*/ */
@ -304,3 +314,73 @@ public enum RewindPolicy {
case transaction(_ transaction: TransactionEntity) case transaction(_ transaction: TransactionEntity)
case quick case quick
} }
extension SyncStatus {
public static func == (lhs: SyncStatus, rhs: SyncStatus) -> Bool {
switch lhs {
case .unprepared:
if case .unprepared = rhs {
return true
} else {
return false
}
case .disconnected:
if case .disconnected = rhs {
return true
} else {
return false
}
case .downloading:
if case .downloading = rhs {
return true
} else {
return false
}
case .validating:
if case .validating = rhs {
return true
} else {
return false
}
case .scanning:
if case .scanning = rhs {
return true
} else {
return false
}
case .enhancing:
if case .enhancing = rhs {
return true
} else {
return false
}
case .fetching:
if case .fetching = rhs {
return true
} else {
return false
}
case .synced:
if case .synced = rhs {
return true
} else {
return false
}
case .stopped:
if case .stopped = rhs {
return true
} else {
return false
}
case .error:
if case .error = rhs {
return true
} else {
return false
}
}
}
}

View File

@ -91,6 +91,8 @@ public extension Notification.Name {
- Note: query userInfo on NotificationKeys.error for an error - Note: query userInfo on NotificationKeys.error for an error
*/ */
static let synchronizerFailed = Notification.Name("SDKSynchronizerFailed") static let synchronizerFailed = Notification.Name("SDKSynchronizerFailed")
static let synchronizerConnectionStateChanged = Notification.Name("SynchronizerConnectionStateChanged")
} }
/** /**
@ -107,9 +109,11 @@ public class SDKSynchronizer: Synchronizer {
public static let error = "SDKSynchronizer.error" public static let error = "SDKSynchronizer.error"
public static let currentStatus = "SDKSynchronizer.currentStatus" public static let currentStatus = "SDKSynchronizer.currentStatus"
public static let nextStatus = "SDKSynchronizer.nextStatus" public static let nextStatus = "SDKSynchronizer.nextStatus"
public static let currentConnectionState = "SDKSynchronizer.currentConnectionState"
public static let previousConnectionState = "SDKSynchronizer.previousConnectionState"
} }
public private(set) var status: Status { public private(set) var status: SyncStatus {
didSet { didSet {
notify(status: status) notify(status: status)
} }
@ -140,7 +144,7 @@ public class SDKSynchronizer: Synchronizer {
} }
init(status: Status, init(status: SyncStatus,
initializer: Initializer, initializer: Initializer,
transactionManager: OutboundTransactionManager, transactionManager: OutboundTransactionManager,
transactionRepository: TransactionRepository, transactionRepository: TransactionRepository,
@ -187,7 +191,7 @@ public class SDKSynchronizer: Synchronizer {
assert(true,"warning: synchronizer started when already started") // TODO: remove this assertion sometime in the near future assert(true,"warning: synchronizer started when already started") // TODO: remove this assertion sometime in the near future
LoggerProxy.debug("warning: synchronizer started when already started") LoggerProxy.debug("warning: synchronizer started when already started")
return return
case .stopped, .synced,.disconnected: case .stopped, .synced,.disconnected, .error:
do { do {
try blockProcessor.start(retry: retry) try blockProcessor.start(retry: retry)
} catch { } catch {
@ -269,9 +273,30 @@ public class SDKSynchronizer: Synchronizer {
name: Notification.Name.blockProcessorFoundTransactions, name: Notification.Name.blockProcessorFoundTransactions,
object: processor) object: processor)
center.addObserver(self,
selector: #selector(connectivityStateChanged(_:)),
name: Notification.Name.blockProcessorConnectivityStateChanged,
object: processor)
} }
// MARK: Block Processor notifications // MARK: Block Processor notifications
@objc func connectivityStateChanged(_ notification: Notification) {
guard let userInfo = notification.userInfo,
let previous = userInfo[CompactBlockProcessorNotificationKey.previousConnectivityStatus] as? ConnectivityState,
let current = userInfo[CompactBlockProcessorNotificationKey.currentConnectivityStatus] as? ConnectivityState else {
LoggerProxy.error("found \(Notification.Name.blockProcessorConnectivityStateChanged) but lacks dictionary information. this is probably a programming error")
return
}
NotificationCenter.default.post(
name: .synchronizerConnectionStateChanged,
object: self,
userInfo: [
NotificationKeys.previousConnectionState : ConnectionState(previous),
NotificationKeys.currentConnectionState : ConnectionState(current)
])
}
@objc func transactionsFound(_ notification: Notification) { @objc func transactionsFound(_ notification: Notification) {
guard let userInfo = notification.userInfo, guard let userInfo = notification.userInfo,
@ -310,41 +335,41 @@ public class SDKSynchronizer: Synchronizer {
@objc func processorStartedDownloading(_ notification: Notification) { @objc func processorStartedDownloading(_ notification: Notification) {
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self = self else { return } guard let self = self, self.status != .downloading(NullProgress()) else { return }
self.status = .downloading self.status = .downloading(NullProgress())
} }
} }
@objc func processorStartedValidating(_ notification: Notification) { @objc func processorStartedValidating(_ notification: Notification) {
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self = self else { return } guard let self = self, self.status != .validating else { return }
self.status = .validating self.status = .validating
} }
} }
@objc func processorStartedScanning(_ notification: Notification) { @objc func processorStartedScanning(_ notification: Notification) {
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self = self else { return } guard let self = self, self.status != .scanning(NullProgress()) else { return }
self.status = .scanning self.status = .scanning(NullProgress())
} }
} }
@objc func processorStartedEnhancing(_ notification: Notification) { @objc func processorStartedEnhancing(_ notification: Notification) {
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self = self else { return } guard let self = self, self.status != .enhancing(NullEnhancementProgress()) else { return }
self.status = .enhancing self.status = .enhancing(NullEnhancementProgress())
} }
} }
@objc func processorStartedFetching(_ notification: Notification) { @objc func processorStartedFetching(_ notification: Notification) {
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self = self else { return } guard let self = self, self.status != .fetching else { return }
self.status = .fetching self.status = .fetching
} }
} }
@objc func processorStopped(_ notification: Notification) { @objc func processorStopped(_ notification: Notification) {
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self = self else { return } guard let self = self, self.status != .stopped else { return }
self.status = .stopped self.status = .stopped
} }
} }
@ -355,10 +380,11 @@ public class SDKSynchronizer: Synchronizer {
guard let self = self else { return } guard let self = self else { return }
if let error = notification.userInfo?[CompactBlockProcessorNotificationKey.error] as? Error { if let error = notification.userInfo?[CompactBlockProcessorNotificationKey.error] as? Error {
self.notifyFailure(error) self.notifyFailure(error)
self.status = .error(self.mapError(error))
} else { } else {
self.notifyFailure(CompactBlockProcessorError.generalError(message: "This is strange. processorFailed Call received no error message")) self.notifyFailure(CompactBlockProcessorError.generalError(message: "This is strange. processorFailed Call received no error message"))
self.status = .error(SynchronizerError.generalError(message: "This is strange. processorFailed Call received no error message"))
} }
self.status = .disconnected
} }
} }
@ -619,7 +645,7 @@ public class SDKSynchronizer: Synchronizer {
NotificationCenter.default.post(name: Notification.Name.synchronizerProgressUpdated, object: self, userInfo: userInfo) NotificationCenter.default.post(name: Notification.Name.synchronizerProgressUpdated, object: self, userInfo: userInfo)
} }
private func notifyStatusChange(newValue: Status, oldValue: Status) { private func notifyStatusChange(newValue: SyncStatus, oldValue: SyncStatus) {
NotificationCenter.default.post(name: .synchronizerStatusWillUpdate, NotificationCenter.default.post(name: .synchronizerStatusWillUpdate,
object: self, object: self,
userInfo: userInfo:
@ -627,7 +653,7 @@ public class SDKSynchronizer: Synchronizer {
NotificationKeys.nextStatus : newValue ]) NotificationKeys.nextStatus : newValue ])
} }
private func notify(status: Status) { private func notify(status: SyncStatus) {
switch status { switch status {
case .disconnected: case .disconnected:
@ -649,6 +675,8 @@ public class SDKSynchronizer: Synchronizer {
NotificationCenter.default.post(name: Notification.Name.synchronizerEnhancing, object: self) NotificationCenter.default.post(name: Notification.Name.synchronizerEnhancing, object: self)
case .fetching: case .fetching:
NotificationCenter.default.post(name: Notification.Name.synchronizerFetching, object: self) NotificationCenter.default.post(name: Notification.Name.synchronizerFetching, object: self)
case .error(let e):
self.notifyFailure(e)
} }
} }
// MARK: book keeping // MARK: book keeping
@ -760,3 +788,44 @@ extension SDKSynchronizer {
(try? self.allReceivedTransactions()) ?? [ConfirmedTransactionEntity]() (try? self.allReceivedTransactions()) ?? [ConfirmedTransactionEntity]()
} }
} }
import GRPC
extension ConnectionState {
init(_ connectivityState: ConnectivityState) {
switch connectivityState {
case .connecting:
self = .connecting
case .idle:
self = .idle
case .ready:
self = .online
case .shutdown:
self = .shutdown
case .transientFailure:
self = .reconnecting
}
}
}
fileprivate struct NullEnhancementProgress: EnhancementProgress {
var totalTransactions: Int { 0 }
var enhancedTransactions: Int { 0 }
var lastFoundTransaction: ConfirmedTransactionEntity? { nil }
var range: CompactBlockRange { 0 ... 0 }
}
fileprivate struct NullProgress: BlockProgressReporting {
var startHeight: BlockHeight {
0
}
var targetHeight: BlockHeight {
0
}
var progressHeight: BlockHeight {
0
}
}