Files
raspberry/iOS/Wallet/Sources/Account/Service/AccountServiceHistory.swift

215 lines
7.3 KiB
Swift

//
// AccountServiceHistory.swift
// Wallet
//
// Created by Saveliy Stavitsky on 1/12/21.
// Copyright © 2021 List. All rights reserved.
//
import RealmSwift
import Foundation
import WalletFoundation
import WalletNetwork
let historyFetchTypeIsNode = false
extension Notification.Name {
static let didUpdateHistory = Notification.Name("Account.Service.History.didUpdateHistory")
}
extension Account.Service {
final class AccountHistory {
private let realm: Realm
private let username: String
init(username: String) throws {
self.username = username
var config = Realm.Configuration()
config.objectTypes = [AccountAction.self]
config.deleteRealmIfMigrationNeeded = true
config.fileURL >>- { url in
let nodeUrl = url
.deletingLastPathComponent()
.appendingPathComponent("\(username)_actions.realm")
let hyperionUrl = url
.deletingLastPathComponent()
.appendingPathComponent("\(username)_actions_hyperion.realm")
config.fileURL = historyFetchTypeIsNode ? nodeUrl : hyperionUrl
let fileManager = FileManager.default
if historyFetchTypeIsNode {
if !fileManager.fileExists(atPath: nodeUrl.relativePath),
fileManager.fileExists(atPath: hyperionUrl.relativePath) {
try? fileManager.moveItem(at: hyperionUrl, to: nodeUrl)
}
} else {
if fileManager.fileExists(atPath: nodeUrl.relativePath),
!fileManager.fileExists(atPath: hyperionUrl.relativePath) {
try? fileManager.moveItem(at: nodeUrl, to: hyperionUrl)
}
}
}
self.realm = try Realm(configuration: config)
}
var actions: Results<AccountAction> {
self.realm
.objects(AccountAction.self)
.where { $0.username == self.username }
}
func actions(in trxId: String, ascending: Bool = true) -> Results<AccountAction> {
self.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)) {
let quantityOfActionsBefore = self.actions.count
self.fetch { [weak self] in
guard let self else { return }
if !self.actions.where({ $0.trxId == trxId }).isEmpty || (self.actions.count == quantityOfActionsBefore) {
completion?(self.actions(in: trxId, ascending: ascending))
} else {
self.fetchUntil(trxId: trxId, completion)
}
}
}
}
func fetch(_ completion: (() -> Void)? = nil) {
self.completions.append(completion)
self.fetchHyperion(for: self.username)
}
private(set) var isLoading = false
private var loadedThisSession = Set<Int>()
// MARK: - Hyperion loading
private func fetchHyperion(for username: String, skip: Int = 0) {
guard !self.isLoading else { return }
self.isLoading = true
Network.actions.fetch(
account: username,
limit: self.loadedThisSession.count == 0 ? 1000 : 100,
skip: skip > 0 ? skip : self.loadedThisSession.count,
type: NodeData.self
) { [weak self] actions, total, error in
guard let self else { return }
guard !error.isExist else {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.isLoading = false
self.fetchHyperion(for: username, skip: skip)
}
return
}
self.add(
actions
.filter {
let identity = AccountAction.createIdentity(username: self.username,
actionId: $0.global_sequence)
return !self.realm.object(ofType: AccountAction.self,
forPrimaryKey: identity).isExist
}
.map { AccountAction(action: $0, receiver: self.username) }
)
if actions.contains(where: { self.loadedThisSession.contains($0.global_sequence) }) || actions.count == 0 {
actions
.map { $0.global_sequence }
.forEach { self.loadedThisSession.insert($0) }
self.isLoading = false
self.notifyEveryone()
} else {
let newSkip = skip + actions.count
actions
.map { $0.global_sequence }
.forEach { self.loadedThisSession.insert($0) }
self.isLoading = false
self.fetchHyperion(for: username, skip: newSkip)
}
}
}
private func add(_ actions: [AccountAction]) {
guard actions.count > 0 else { return }
self.write(block: { realm in
actions.forEach { realm.add($0, update: .modified) }
}) { _ in
// self.notifyEveryone()
}
}
private func remove(_ actions: [AccountAction]) {
guard actions.count > 0 else { return }
self.write(block: { realm in
actions.forEach { realm.delete($0) }
}) { _ in
// self.notifyEveryone()
}
}
private var completions = [(() -> Void)?]()
private func notifyEveryone() {
let completions = self.completions
self.completions = []
Notification.post(name: .didUpdateHistory, userInfo: ["username": username])
completions
.compactMap { $0 }
.forEach { $0() }
}
private func write(block: @escaping ((Realm) -> Void), completion: ((Error?) -> Void)? = nil) {
DispatchQueue.main.async {
let realm = self.realm
realm.writeAsync({ block(realm) }, onComplete: completion)
}
}
}
}