// // 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 { self.realm .objects(AccountAction.self) .where { $0.username == self.username } } func actions(in trxId: String, ascending: Bool = true) -> Results { self.actions .filter("trxId = '\(trxId)'") .sorted(by: \.accountActionId, ascending: ascending) } func fetchUntil(trxId: String, ascending: Bool = true, _ completion: ((Results) -> 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() // 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) } } } }