200 lines
8.0 KiB
Swift
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()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|