Files
raspberry/iOS/Wallet/Sources/Account/Service/AccountServiceHistory.swift
2022-05-27 20:21:01 +03:00

200 lines
8.0 KiB
Swift

//
// AccountServiceHistory.swift
// PayCash
//
// Created by Saveliy Stavitsky on 1/12/21.
// Copyright © 2021 List. All rights reserved.
//
import RealmSwift
import Foundation
let historyFetchTypeIsNode = true
extension Notification.Name {
static let didUpdateHistory = Notification.Name("Account.Service.History.didUpdateHistory")
}
extension Account.Service {
class History {
static let shared = History()
private init() {
Accounts().collection.forEach({ try? load(username: $0.username) })
}
private var accountsHistory = [String: Account.Service.AccountHistory]()
func load(username: String) throws {
accountsHistory[username] = try Account.Service.AccountHistory(username: username)
accountsHistory[username]?.fetch()
}
var current: Account.Service.AccountHistory? { accountsHistory[Accounts().current?.username ?? ""] }
func of(username: String) -> Account.Service.AccountHistory? { accountsHistory[username] }
func fetch() { accountsHistory.values.forEach({ $0.fetch() }) }
}
}
extension Account.Service {
class AccountHistory {
private let realm: Realm
private let username: String
init(username: String) throws {
self.username = username
var config = Realm.Configuration()
if historyFetchTypeIsNode {
// Use the default directory, but replace the filename with the username
config.fileURL = config.fileURL!.deletingLastPathComponent()
.appendingPathComponent("\(username)_actions.realm")
} else {
// config.inMemoryIdentifier = "\(username)_actions.realm"
config.fileURL = config.fileURL!.deletingLastPathComponent()
.appendingPathComponent("\(username)_actions_hyperion.realm")
}
config.objectTypes = [AccountAction.self]
config.deleteRealmIfMigrationNeeded = true
realm = try Realm(configuration: config)
}
var actions: Results<AccountAction> { realm.objects(AccountAction.self) }
func actions(in trxId: String, ascending: Bool = true) -> Results<AccountAction> {
actions
.filter("trxId = '\(trxId)'")
.sorted(by: \.accountActionId, ascending: ascending)
}
func fetchUntil(trxId: String, ascending: Bool = true, _ completion: ((Results<AccountAction>) -> Void)? = nil) {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
self.fetch { [unowned self] in
if !actions.filter("trxId = '\(trxId)'").isEmpty {
// if (self?.actions.action(in: trxId).count ?? 0) > 0 {
completion?(actions(in: trxId, ascending: ascending))
} else {
fetchUntil(trxId: trxId, completion)
}
}
}
}
func fetch(_ completion: (() -> Void)? = nil) {
completions.append(completion)
if historyFetchTypeIsNode {
//remove all non irreversible transactions before start sync
remove(realm.objects(AccountAction.self).filter("irreversible = false").map({ $0 }))
fetchNode()
} else {
fetchHyperion()
}
}
private(set) var isLoading = false
private var loadedThisSession = Set<Int>()
// MARK: - Hyperion loading
private func fetchHyperion(skip: Int = 0) {
guard !isLoading else { return }
isLoading = true
Network.actions.fetch(
account: username,
limit: loadedThisSession.count == 0 ? 1000 : 100,
skip: skip,
type: Network.Service.Actions.NodeData.self
) { [unowned self] actions, total, error in
guard error != nil else {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
isLoading = false
fetchHyperion(skip: skip)
}
return
}
add(
actions
.filter { realm.object(ofType: AccountAction.self, forPrimaryKey: $0.global_sequence) == nil }
.map({ AccountAction(action: $0, receiver: username) })
)
if !actions.contains(where: { !loadedThisSession.contains($0.global_sequence) }) || actions.count == 0 {
actions.map({ $0.global_sequence }).forEach({ loadedThisSession.insert($0) })
isLoading = false
notifyEveryone()
} else {
let newSkip = skip + (loadedThisSession.count == 0 ? 900 : 90)
actions.map({ $0.global_sequence }).forEach({ loadedThisSession.insert($0) })
isLoading = false
fetchHyperion(skip: newSkip)
}
}
}
// MARK: - Node loading
private func add(_ actions: [AccountAction]) {
guard actions.count > 0 else { return }
try! realm.write { //swiftlint:disable:this force_try
for action in actions { realm.add(action) }
}
// Notification.post(name: .didUpdateHistory, userInfo: ["username": self?.username ?? ""])
}
private func remove(_ actions: [AccountAction]) {
guard actions.count > 0 else { return }
try! realm.write { //swiftlint:disable:this force_try
for action in actions { realm.delete(action) }
}
}
private var completions: [(() -> Void)?] = []
private func notifyEveryone() {
let completions = self.completions
self.completions = []
Notification.post(name: .didUpdateHistory, userInfo: ["username": username])
for completion in completions.compactMap({ $0 }) {
completion()
}
}
private func fetchNode(pos: Int? = nil, maxPos: Int? = nil) {
guard !isLoading else { return }
isLoading = true
//Check actions that will be fetched not already presented in database else skip
if let pos = pos {
var skip = true
let from = pos - 100
for index in stride(from: from, to: pos, by: 1) where index > 0 {
if realm.object(ofType: AccountAction.self, forPrimaryKey: index) == nil {
skip = false
break
}
}
if skip {
isLoading = false
if (realm.objects(AccountAction.self).count - 1) < maxPos ?? pos - 1, from > 0 {
fetchNode(pos: from, maxPos: maxPos ?? pos - 1)
} else {
notifyEveryone()
}
return
}
}
Network.actions.fetchNode(account: username, limit: 100, skip: pos) { [weak self] actions, error in
guard let welf = self else { return }
if error != nil {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
welf.isLoading = false
welf.fetchNode(pos: pos, maxPos: maxPos)
}
return
}
welf.add(actions.filter { welf.realm.object(ofType: AccountAction.self, forPrimaryKey: $0.account_action_seq) == nil }.map(AccountAction.init))
welf.isLoading = false
if (welf.realm.objects(AccountAction.self).count - 1) < maxPos ?? actions.last?.account_action_seq ?? 0, actions.count > 0 {
welf.fetchNode(pos: actions.first?.account_action_seq, maxPos: maxPos ?? actions.last?.account_action_seq)
} else {
welf.notifyEveryone()
}
}
}
}
}