stack_wallet/lib/db/hive/db.dart

359 lines
12 KiB
Dart
Raw Normal View History

2023-05-26 21:21:16 +00:00
/*
* 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
*
*/
2022-08-26 08:11:35 +00:00
import 'dart:isolate';
import 'package:compat/compat.dart' as lib_monero_compat;
2024-06-10 22:50:26 +00:00
import 'package:hive/hive.dart' show Box;
import 'package:hive/src/hive_impl.dart';
2022-08-26 08:11:35 +00:00
import 'package:mutex/mutex.dart';
2024-05-29 19:29:45 +00:00
import '../../app_config.dart';
import '../../models/exchange/response_objects/trade.dart';
import '../../models/node_model.dart';
import '../../models/notification_model.dart';
import '../../models/trade_wallet_lookup.dart';
import '../../services/wallets_service.dart';
import '../../utilities/logger.dart';
import '../../wallets/crypto_currency/crypto_currency.dart';
2022-08-26 08:11:35 +00:00
class DB {
2024-06-10 22:50:26 +00:00
final hive = HiveImpl();
// legacy (required for migrations)
@Deprecated("Left over for migration from old versions of Stack Wallet")
2022-08-26 08:11:35 +00:00
static const String boxNameAddressBook = "addressBook";
static const String boxNameTrades = "exchangeTransactionsBox";
2023-09-18 21:28:31 +00:00
static const String boxNameAllWalletsData = "wallets";
static const String boxNameFavoriteWallets = "favoriteWallets";
// in use
// TODO: migrate
2022-08-26 08:11:35 +00:00
static const String boxNameNodeModels = "nodeModels";
static const String boxNamePrimaryNodes = "primaryNodes";
static const String boxNameNotifications = "notificationModels";
static const String boxNameWatchedTransactions =
"watchedTxNotificationModels";
static const String boxNameWatchedTrades = "watchedTradesNotificationModels";
static const String boxNameTradesV2 = "exchangeTradesBox";
2022-08-26 08:11:35 +00:00
static const String boxNameTradeNotes = "tradeNotesBox";
static const String boxNameTradeLookup = "tradeToTxidLookUpBox";
static const String boxNameWalletsToDeleteOnStart = "walletsToDeleteOnStart";
static const String boxNamePriceCache = "priceAPIPrice24hCache";
// in use (keep for now)
static const String boxNameDBInfo = "dbInfo";
static const String boxNamePrefs = "prefs";
2023-09-20 20:39:06 +00:00
static const String boxNameOneTimeDialogsShown = "oneTimeDialogsShown";
2022-08-26 08:11:35 +00:00
2024-05-15 21:20:45 +00:00
String _boxNameTxCache({required CryptoCurrency currency}) =>
"${currency.identifier}_txCache";
// firo only
2024-05-15 21:20:45 +00:00
String _boxNameSetCache({required CryptoCurrency currency}) =>
"${currency.identifier}_anonymitySetCache";
String _boxNameUsedSerialsCache({required CryptoCurrency currency}) =>
"${currency.identifier}_usedSerialsCache";
2022-08-26 08:11:35 +00:00
Box<NodeModel>? _boxNodeModels;
Box<NodeModel>? _boxPrimaryNodes;
Box<dynamic>? _boxAllWalletsData;
Box<NotificationModel>? _boxNotifications;
Box<NotificationModel>? _boxWatchedTransactions;
Box<NotificationModel>? _boxWatchedTrades;
Box<Trade>? _boxTradesV2;
Box<String>? _boxTradeNotes;
Box<String>? _boxFavoriteWallets;
Box<lib_monero_compat.WalletInfo>? _walletInfoSource;
Box<dynamic>? _boxPrefs;
Box<TradeWalletLookup>? _boxTradeLookup;
Box<dynamic>? _boxDBInfo;
// Box<String>? _boxDesktopData;
2022-08-26 08:11:35 +00:00
final Map<String, Box<dynamic>> _walletBoxes = {};
2024-05-15 21:20:45 +00:00
final Map<String, Box<dynamic>> _txCacheBoxes = {};
final Map<String, Box<dynamic>> _setCacheBoxes = {};
final Map<String, Box<dynamic>> _usedSerialsCacheBoxes = {};
final Map<String, Box<dynamic>> _getSparkUsedCoinsTagsCacheBoxes = {};
2022-08-26 08:11:35 +00:00
// exposed for monero
Box<lib_monero_compat.WalletInfo> get moneroWalletInfoBox =>
_walletInfoSource!;
2022-08-26 08:11:35 +00:00
// mutex for stack backup
final mutex = Mutex();
DB._();
static final DB _instance = DB._();
static DB get instance {
// "This name does not uniquely identify an isolate. Multiple isolates in the same process may have the same debugName."
// if (Isolate.current.debugName != "main") {
// TODO: make sure this works properly
if (Isolate.current.debugName != "main") {
throw Exception(
2024-05-15 21:20:45 +00:00
"DB.instance should not be accessed outside the main isolate!",
);
2022-08-26 08:11:35 +00:00
}
return _instance;
}
// open hive boxes
Future<void> init() async {
2024-06-10 22:50:26 +00:00
if (hive.isBoxOpen(boxNameDBInfo)) {
_boxDBInfo = hive.box<dynamic>(boxNameDBInfo);
} else {
2024-06-10 22:50:26 +00:00
_boxDBInfo = await hive.openBox<dynamic>(boxNameDBInfo);
}
2024-06-10 22:50:26 +00:00
await hive.openBox<String>(boxNameWalletsToDeleteOnStart);
2024-06-10 22:50:26 +00:00
if (hive.isBoxOpen(boxNamePrefs)) {
_boxPrefs = hive.box<dynamic>(boxNamePrefs);
} else {
2024-06-10 22:50:26 +00:00
_boxPrefs = await hive.openBox<dynamic>(boxNamePrefs);
2022-08-26 08:11:35 +00:00
}
2024-06-10 22:50:26 +00:00
if (hive.isBoxOpen(boxNameNodeModels)) {
_boxNodeModels = hive.box<NodeModel>(boxNameNodeModels);
} else {
2024-06-10 22:50:26 +00:00
_boxNodeModels = await hive.openBox<NodeModel>(boxNameNodeModels);
}
2024-06-10 22:50:26 +00:00
if (hive.isBoxOpen(boxNamePrimaryNodes)) {
_boxPrimaryNodes = hive.box<NodeModel>(boxNamePrimaryNodes);
} else {
2024-06-10 22:50:26 +00:00
_boxPrimaryNodes = await hive.openBox<NodeModel>(boxNamePrimaryNodes);
}
2024-06-10 22:50:26 +00:00
if (hive.isBoxOpen(boxNameAllWalletsData)) {
_boxAllWalletsData = hive.box<dynamic>(boxNameAllWalletsData);
} else {
2024-06-10 22:50:26 +00:00
_boxAllWalletsData = await hive.openBox<dynamic>(boxNameAllWalletsData);
}
_boxNotifications =
2024-06-10 22:50:26 +00:00
await hive.openBox<NotificationModel>(boxNameNotifications);
_boxWatchedTransactions =
2024-06-10 22:50:26 +00:00
await hive.openBox<NotificationModel>(boxNameWatchedTransactions);
_boxWatchedTrades =
2024-06-10 22:50:26 +00:00
await hive.openBox<NotificationModel>(boxNameWatchedTrades);
_boxTradesV2 = await hive.openBox<Trade>(boxNameTradesV2);
_boxTradeNotes = await hive.openBox<String>(boxNameTradeNotes);
_boxTradeLookup = await hive.openBox<TradeWalletLookup>(boxNameTradeLookup);
_walletInfoSource = await hive.openBox<lib_monero_compat.WalletInfo>(
lib_monero_compat.WalletInfo.boxName);
2024-06-10 22:50:26 +00:00
_boxFavoriteWallets = await hive.openBox<String>(boxNameFavoriteWallets);
await Future.wait([
2024-06-10 22:50:26 +00:00
hive.openBox<dynamic>(boxNamePriceCache),
_loadWalletBoxes(),
]);
2022-08-26 08:11:35 +00:00
}
Future<void> _loadWalletBoxes() async {
final names = _boxAllWalletsData!.get("names") as Map? ?? {};
names.removeWhere((name, dyn) {
final jsonObject = Map<String, dynamic>.from(dyn as Map);
try {
AppConfig.getCryptoCurrencyFor(jsonObject["coin"] as String);
return false;
} catch (e, s) {
Logging.instance.log(
2024-05-15 21:20:45 +00:00
"Error, ${jsonObject["coin"]} does not exist, $name wallet cannot be loaded",
level: LogLevel.Error,
);
return true;
}
});
2024-05-15 21:20:45 +00:00
final mapped = Map<String, dynamic>.from(names).map(
(name, dyn) => MapEntry(
name,
WalletInfo.fromJson(Map<String, dynamic>.from(dyn as Map)),
),
);
2022-08-26 08:11:35 +00:00
for (final entry in mapped.entries) {
2024-06-10 22:50:26 +00:00
if (hive.isBoxOpen(entry.value.walletId)) {
_walletBoxes[entry.value.walletId] =
2024-06-10 22:50:26 +00:00
hive.box<dynamic>(entry.value.walletId);
} else {
_walletBoxes[entry.value.walletId] =
2024-06-10 22:50:26 +00:00
await hive.openBox<dynamic>(entry.value.walletId);
}
2022-08-26 08:11:35 +00:00
}
}
2024-05-15 21:20:45 +00:00
Future<Box<dynamic>> getTxCacheBox({required CryptoCurrency currency}) async {
if (_txCacheBoxes[currency.identifier]?.isOpen != true) {
_txCacheBoxes.remove(currency.identifier);
2023-07-25 16:49:49 +00:00
}
2024-05-15 21:20:45 +00:00
return _txCacheBoxes[currency.identifier] ??=
2024-06-10 22:50:26 +00:00
await hive.openBox<dynamic>(_boxNameTxCache(currency: currency));
}
2024-05-15 21:20:45 +00:00
Future<void> closeTxCacheBox({required CryptoCurrency currency}) async {
await _txCacheBoxes[currency.identifier]?.close();
}
2024-05-15 21:20:45 +00:00
Future<Box<dynamic>> getAnonymitySetCacheBox({
required CryptoCurrency currency,
}) async {
if (_setCacheBoxes[currency.identifier]?.isOpen != true) {
_setCacheBoxes.remove(currency.identifier);
2023-07-25 16:49:49 +00:00
}
2024-05-15 21:20:45 +00:00
return _setCacheBoxes[currency.identifier] ??=
2024-06-10 22:50:26 +00:00
await hive.openBox<dynamic>(_boxNameSetCache(currency: currency));
}
2024-05-15 21:20:45 +00:00
Future<void> closeAnonymitySetCacheBox({
required CryptoCurrency currency,
}) async {
await _setCacheBoxes[currency.identifier]?.close();
}
2024-05-15 21:20:45 +00:00
Future<Box<dynamic>> getUsedSerialsCacheBox({
required CryptoCurrency currency,
}) async {
if (_usedSerialsCacheBoxes[currency.identifier]?.isOpen != true) {
_usedSerialsCacheBoxes.remove(currency.identifier);
2023-07-25 16:49:49 +00:00
}
2024-05-15 21:20:45 +00:00
return _usedSerialsCacheBoxes[currency.identifier] ??=
2024-06-10 22:50:26 +00:00
await hive.openBox<dynamic>(
2024-05-15 21:20:45 +00:00
_boxNameUsedSerialsCache(currency: currency),
);
}
2024-05-15 21:20:45 +00:00
Future<void> closeUsedSerialsCacheBox({
required CryptoCurrency currency,
}) async {
await _usedSerialsCacheBoxes[currency.identifier]?.close();
}
/// Clear all cached transactions for the specified coin
2024-05-15 21:20:45 +00:00
Future<void> clearSharedTransactionCache({
required CryptoCurrency currency,
}) async {
await deleteAll<dynamic>(boxName: _boxNameTxCache(currency: currency));
if (currency is Firo) {
await deleteAll<dynamic>(boxName: _boxNameSetCache(currency: currency));
await deleteAll<dynamic>(
2024-05-15 21:20:45 +00:00
boxName: _boxNameUsedSerialsCache(currency: currency),
);
2022-08-26 08:11:35 +00:00
}
}
/////////////////////////////////////////
Future<void> addWalletBox({required String walletId}) async {
if (_walletBoxes[walletId] != null) {
throw Exception("Attempted overwrite of existing wallet box!");
}
2024-06-10 22:50:26 +00:00
_walletBoxes[walletId] = await hive.openBox<dynamic>(walletId);
2022-08-26 08:11:35 +00:00
}
Future<void> removeWalletBox({required String walletId}) async {
_walletBoxes.remove(walletId);
}
///////////////////////////////////////////
// reads
List<dynamic> keys<T>({required String boxName}) =>
2024-06-10 22:50:26 +00:00
hive.box<T>(boxName).keys.toList(growable: false);
2022-08-26 08:11:35 +00:00
List<T> values<T>({required String boxName}) =>
2024-06-10 22:50:26 +00:00
hive.box<T>(boxName).values.toList(growable: false);
2022-08-26 08:11:35 +00:00
T? get<T>({
required String boxName,
required dynamic key,
}) =>
2024-06-10 22:50:26 +00:00
hive.box<T>(boxName).get(key);
2022-08-26 08:11:35 +00:00
bool containsKey<T>({required String boxName, required dynamic key}) =>
2024-06-10 22:50:26 +00:00
hive.box<T>(boxName).containsKey(key);
2022-08-26 08:11:35 +00:00
// writes
2024-05-15 21:20:45 +00:00
Future<void> put<T>({
required String boxName,
required dynamic key,
required T value,
}) async =>
2022-08-26 08:11:35 +00:00
await mutex
2024-06-10 22:50:26 +00:00
.protect(() async => await hive.box<T>(boxName).put(key, value));
2022-08-26 08:11:35 +00:00
Future<void> add<T>({required String boxName, required T value}) async =>
2024-06-10 22:50:26 +00:00
await mutex.protect(() async => await hive.box<T>(boxName).add(value));
2022-08-26 08:11:35 +00:00
2024-05-15 21:20:45 +00:00
Future<void> addAll<T>({
required String boxName,
required Iterable<T> values,
}) async =>
2022-08-26 08:11:35 +00:00
await mutex
2024-06-10 22:50:26 +00:00
.protect(() async => await hive.box<T>(boxName).addAll(values));
2022-08-26 08:11:35 +00:00
2024-05-15 21:20:45 +00:00
Future<void> delete<T>({
required dynamic key,
required String boxName,
}) async =>
2024-06-10 22:50:26 +00:00
await mutex.protect(() async => await hive.box<T>(boxName).delete(key));
2022-08-26 08:11:35 +00:00
Future<void> deleteAll<T>({required String boxName}) async {
await mutex.protect(() async {
2024-06-10 22:50:26 +00:00
final box = await hive.openBox<T>(boxName);
await box.clear();
});
}
2022-08-26 08:11:35 +00:00
Future<void> deleteBoxFromDisk({required String boxName}) async =>
2024-06-10 22:50:26 +00:00
await mutex.protect(() async => await hive.deleteBoxFromDisk(boxName));
///////////////////////////////////////////////////////////////////////////
Future<bool> deleteEverything() async {
try {
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameAddressBook);
await DB.instance.deleteBoxFromDisk(boxName: "debugInfoBox");
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameNodeModels);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNamePrimaryNodes);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameAllWalletsData);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameNotifications);
await DB.instance
.deleteBoxFromDisk(boxName: DB.boxNameWatchedTransactions);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameWatchedTrades);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTrades);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTradesV2);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTradeNotes);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTradeLookup);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameFavoriteWallets);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNamePrefs);
await DB.instance
.deleteBoxFromDisk(boxName: DB.boxNameWalletsToDeleteOnStart);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNamePriceCache);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameDBInfo);
await DB.instance.deleteBoxFromDisk(boxName: "theme");
return true;
} catch (e, s) {
Logging.instance.log("$e $s", level: LogLevel.Error);
return false;
}
}
2022-08-26 08:11:35 +00:00
}
abstract class DBKeys {
static const String cachedBalance = "cachedBalance";
2023-01-12 19:21:03 +00:00
static const String cachedBalanceSecondary = "cachedBalanceSecondary";
static const String isFavorite = "isFavorite";
static const String id = "id";
static const String storedChainHeight = "storedChainHeight";
}