/* * This file is part of Stack Wallet. * * Copyright (c) 2023 Cypher Stack * All Rights Reserved. * The code is distributed under GPLv3 license, see LICENSE file for details. * Generated by Cypher Stack on 2023-05-26 * */ import 'package:flutter/foundation.dart'; import 'package:isar/isar.dart'; import 'package:tuple/tuple.dart'; import '../../db/hive/db.dart'; import '../../models/exchange/active_pair.dart'; import '../../models/exchange/aggregate_currency.dart'; import '../../models/isar/exchange_cache/currency.dart'; import '../../models/isar/exchange_cache/pair.dart'; import '../../utilities/enums/exchange_rate_type_enum.dart'; import '../../utilities/logger.dart'; import '../../utilities/prefs.dart'; import '../../utilities/stack_file_system.dart'; import 'change_now/change_now_exchange.dart'; import 'majestic_bank/majestic_bank_exchange.dart'; import 'nanswap/nanswap_exchange.dart'; import 'trocador/trocador_exchange.dart'; class ExchangeDataLoadingService { ExchangeDataLoadingService._(); static final ExchangeDataLoadingService _instance = ExchangeDataLoadingService._(); static ExchangeDataLoadingService get instance => _instance; Isar? _isar; Isar get isar => _isar!; VoidCallback? onLoadingError; VoidCallback? onLoadingComplete; static const int cacheVersion = 1; static int get currentCacheVersion => DB.instance.get( boxName: DB.boxNameDBInfo, key: "exchange_data_cache_version", ) as int? ?? 0; Future _updateCurrentCacheVersion(int version) async { await DB.instance.put( boxName: DB.boxNameDBInfo, key: "exchange_data_cache_version", value: version, ); } Future initDB() async { if (_isar != null) return; await _isar?.close(); _isar = await Isar.open( [ CurrencySchema, // PairSchema, ], directory: (await StackFileSystem.applicationIsarDirectory()).path, // inspector: kDebugMode, inspector: false, name: "exchange_cache", maxSizeMiB: 64, ); } Future setCurrenciesIfEmpty( ActivePair? pair, ExchangeRateType rateType, ) async { if (pair?.send == null && pair?.receive == null) { if (await isar.currencies.count() > 0) { pair?.setSend( await getAggregateCurrency( "BTC", rateType, null, ), notifyListeners: false, ); pair?.setReceive( await getAggregateCurrency( "XMR", rateType, null, ), notifyListeners: false, ); } } } Future getAggregateCurrency( String ticker, ExchangeRateType rateType, String? contract, ) async { final currencies = await ExchangeDataLoadingService.instance.isar.currencies .filter() .group( (q) => rateType == ExchangeRateType.fixed ? q .rateTypeEqualTo(SupportedRateType.both) .or() .rateTypeEqualTo(SupportedRateType.fixed) : q .rateTypeEqualTo(SupportedRateType.both) .or() .rateTypeEqualTo(SupportedRateType.estimated), ) .and() .tickerEqualTo( ticker, caseSensitive: false, ) .and() .tokenContractEqualTo(contract) .findAll(); final items = currencies .map((e) => Tuple2(e.exchangeName, e)) .toList(growable: false); return items.isNotEmpty ? AggregateCurrency(exchangeCurrencyPairs: items) : null; } bool get isLoading => _locked; bool _locked = false; Future loadAll() async { if (!_locked) { _locked = true; if (_isar == null) { await initDB(); } Logging.instance.log( "ExchangeDataLoadingService.loadAll starting...", level: LogLevel.Info, ); final start = DateTime.now(); try { /* // Old exchange data loading code. await Future.wait([ _loadChangeNowCurrencies(), // _loadChangeNowFixedRatePairs(), // _loadChangeNowEstimatedRatePairs(), // loadSimpleswapFixedRateCurrencies(ref), // loadSimpleswapFloatingRateCurrencies(ref), loadMajesticBankCurrencies(), loadTrocadorCurrencies(), ]); // quicker to load available currencies on the fly for a specific base currency // await _loadChangeNowFixedRatePairs(); // await _loadChangeNowEstimatedRatePairs(); */ // Exchanges which support Tor just get treated normally. final futures = [ loadMajesticBankCurrencies(), loadTrocadorCurrencies(), loadNanswapCurrencies(), ]; // If using Tor, don't load data for exchanges which don't support Tor. // // Add to this list when adding an exchange which doesn't supports Tor. if (!Prefs.instance.useTor) { futures.add(_loadChangeNowCurrencies()); } // wait for all loading futures to complete await Future.wait(futures); Logging.instance.log( "ExchangeDataLoadingService.loadAll finished in ${DateTime.now().difference(start).inSeconds} seconds", level: LogLevel.Info, ); onLoadingComplete?.call(); await _updateCurrentCacheVersion(cacheVersion); } catch (e, s) { Logging.instance.log( "ExchangeDataLoadingService.loadAll failed after ${DateTime.now().difference(start).inSeconds} seconds: $e\n$s", level: LogLevel.Error, ); onLoadingError?.call(); } _locked = false; } } Future _loadChangeNowCurrencies() async { if (_isar == null) { await initDB(); } final exchange = ChangeNowExchange.instance; final responseCurrencies = await exchange.getAllCurrencies(false); if (responseCurrencies.value != null) { await isar.writeTxn(() async { final idsToDelete = await isar.currencies .where() .exchangeNameEqualTo(ChangeNowExchange.exchangeName) .idProperty() .findAll(); await isar.currencies.deleteAll(idsToDelete); await isar.currencies.putAll(responseCurrencies.value!); }); } else { Logging.instance.log( "Failed to load changeNOW currencies: ${responseCurrencies.exception?.message}", level: LogLevel.Error, ); return; } } // Future _loadChangeNowFixedRatePairs() async { // final exchange = ChangeNowExchange.instance; // // final responsePairs = await compute(exchange.getAllPairs, true); // // if (responsePairs.value != null) { // await isar.writeTxn(() async { // final idsToDelete2 = await isar.pairs // .where() // .exchangeNameEqualTo(ChangeNowExchange.exchangeName) // .filter() // .rateTypeEqualTo(SupportedRateType.fixed) // .idProperty() // .findAll(); // await isar.pairs.deleteAll(idsToDelete2); // await isar.pairs.putAll(responsePairs.value!); // }); // } else { // Logging.instance.log( // "Failed to load changeNOW available fixed rate pairs: ${responsePairs.exception?.message}", // level: LogLevel.Error); // return; // } // } // Future _loadChangeNowEstimatedRatePairs() async { // final exchange = ChangeNowExchange.instance; // // final responsePairs = await compute(exchange.getAllPairs, false); // // if (responsePairs.value != null) { // await isar.writeTxn(() async { // final idsToDelete = await isar.pairs // .where() // .exchangeNameEqualTo(ChangeNowExchange.exchangeName) // .filter() // .rateTypeEqualTo(SupportedRateType.estimated) // .idProperty() // .findAll(); // await isar.pairs.deleteAll(idsToDelete); // await isar.pairs.putAll(responsePairs.value!); // }); // } else { // Logging.instance.log( // "Failed to load changeNOW available floating rate pairs: ${responsePairs.exception?.message}", // level: LogLevel.Error); // return; // } // } // // Future loadSimpleswapFloatingRateCurrencies(WidgetRef ref) async { // final exchange = SimpleSwapExchange(); // final responseCurrencies = await exchange.getAllCurrencies(false); // // if (responseCurrencies.value != null) { // ref // .read(availableSimpleswapCurrenciesProvider) // .updateFloatingCurrencies(responseCurrencies.value!); // // final responsePairs = await exchange.getAllPairs(false); // // if (responsePairs.value != null) { // ref // .read(availableSimpleswapCurrenciesProvider) // .updateFloatingPairs(responsePairs.value!); // } else { // Logging.instance.log( // "loadSimpleswapFloatingRateCurrencies: $responsePairs", // level: LogLevel.Warning, // ); // } // } else { // Logging.instance.log( // "loadSimpleswapFloatingRateCurrencies: $responseCurrencies", // level: LogLevel.Warning, // ); // } // } // // Future loadSimpleswapFixedRateCurrencies(WidgetRef ref) async { // final exchange = SimpleSwapExchange(); // final responseCurrencies = await exchange.getAllCurrencies(true); // // if (responseCurrencies.value != null) { // ref // .read(availableSimpleswapCurrenciesProvider) // .updateFixedCurrencies(responseCurrencies.value!); // // final responsePairs = await exchange.getAllPairs(true); // // if (responsePairs.value != null) { // ref // .read(availableSimpleswapCurrenciesProvider) // .updateFixedPairs(responsePairs.value!); // } else { // Logging.instance.log( // "loadSimpleswapFixedRateCurrencies: $responsePairs", // level: LogLevel.Warning, // ); // } // } else { // Logging.instance.log( // "loadSimpleswapFixedRateCurrencies: $responseCurrencies", // level: LogLevel.Warning, // ); // } // } Future loadMajesticBankCurrencies() async { if (_isar == null) { await initDB(); } final exchange = MajesticBankExchange.instance; final responseCurrencies = await exchange.getAllCurrencies(false); if (responseCurrencies.value != null) { await isar.writeTxn(() async { final idsToDelete = await isar.currencies .where() .exchangeNameEqualTo(MajesticBankExchange.exchangeName) .idProperty() .findAll(); await isar.currencies.deleteAll(idsToDelete); await isar.currencies.putAll(responseCurrencies.value!); }); } else { Logging.instance.log( "loadMajesticBankCurrencies: $responseCurrencies", level: LogLevel.Warning, ); } } Future loadTrocadorCurrencies() async { if (_isar == null) { await initDB(); } final exchange = TrocadorExchange.instance; final responseCurrencies = await exchange.getAllCurrencies(false); if (responseCurrencies.value != null) { await isar.writeTxn(() async { final idsToDelete = await isar.currencies .where() .exchangeNameEqualTo(TrocadorExchange.exchangeName) .idProperty() .findAll(); await isar.currencies.deleteAll(idsToDelete); await isar.currencies.putAll(responseCurrencies.value!); }); } else { Logging.instance.log( "loadTrocadorCurrencies: $responseCurrencies", level: LogLevel.Warning, ); } } Future loadNanswapCurrencies() async { if (_isar == null) { await initDB(); } final responseCurrencies = await NanswapExchange.instance.getAllCurrencies(false); if (responseCurrencies.value != null) { await isar.writeTxn(() async { final idsToDelete = await isar.currencies .where() .exchangeNameEqualTo(NanswapExchange.exchangeName) .idProperty() .findAll(); await isar.currencies.deleteAll(idsToDelete); await isar.currencies.putAll(responseCurrencies.value!); }); } else { Logging.instance.log( "loadNanswapCurrencies: $responseCurrencies", level: LogLevel.Warning, ); } } // Future loadMajesticBankPairs() async { // final exchange = MajesticBankExchange.instance; // // final responsePairs = await exchange.getAllPairs(false); // if (responsePairs.value != null) { // await isar.writeTxn(() async { // final idsToDelete2 = await isar.pairs // .where() // .exchangeNameEqualTo(MajesticBankExchange.exchangeName) // .idProperty() // .findAll(); // await isar.pairs.deleteAll(idsToDelete2); // await isar.pairs.putAll(responsePairs.value!); // }); // } else { // Logging.instance.log( // "loadMajesticBankCurrencies: $responsePairs", // level: LogLevel.Warning, // ); // } // } }