cake_wallet/lib/entities/fs_migration.dart
cyan c78662fbfe
CW 781 replace all print statements with printV (#1733)
* replace all print statements with printV

* restore backup error message

* missing print statements, error fixes

* Update cw_core/lib/utils/print_verbose.dart [skip ci]

* Update cw_core/lib/utils/print_verbose.dart [skip ci]

* CW-846: Correctly display balance (#1848)

* Correctly display balance even with frozen coins

* remove package= from AndroidMainfest.xml

* update namespace

* print -> printV

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
2024-12-09 20:23:59 +02:00

443 lines
14 KiB
Dart

import 'dart:io';
import 'dart:convert';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:collection/collection.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:hive/hive.dart';
import 'package:path_provider/path_provider.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/entities/contact.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/entities/encrypt.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/ios_legacy_helper.dart' as ios_legacy_helper;
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
const reservedNames = ["flutter_assets", "wallets", "db"];
Future<void> migrate_android_v1() async {
final appDocDir = await getApplicationDocumentsDirectory();
await android_migrate_hives(appDocDir: appDocDir);
await android_migrate_wallets(appDocDir: appDocDir);
}
Future<void> ios_migrate_v1(
Box<WalletInfo> walletInfoSource, Box<Trade> tradeSource, Box<Contact> contactSource) async {
final prefs = await SharedPreferences.getInstance();
if (prefs.getBool('ios_migration_v1_completed') ?? false) {
return;
}
await ios_migrate_user_defaults();
await ios_migrate_pin();
await ios_migrate_wallet_passwords();
await ios_migrate_wallet_info(walletInfoSource);
await ios_migrate_trades_list(tradeSource);
await ios_migrate_address_book(contactSource);
await prefs.setBool('ios_migration_v1_completed', true);
}
Future<void> ios_migrate_user_defaults() async {
//get the new shared preferences instance
final prefs = await SharedPreferences.getInstance();
if (prefs.getBool('ios_migration_user_defaults_completed') ?? false) {
return;
}
//translate the node uri
final nodeURI = await ios_legacy_helper.getString('node_uri');
// await prefs.setString('current_node_id', nodeURI);
await prefs.setInt('current_node_id', 0);
//should we provide default btc node key?
final activeCurrency = await ios_legacy_helper.getInt('currency');
if (activeCurrency != null) {
final convertedCurrency = convertFiatLegacy(activeCurrency);
await prefs.setString('current_fiat_currency', convertedCurrency.serialize());
}
//translate fee priority
final activeFeeTier = await ios_legacy_helper.getInt('saved_fee_priority');
if (activeFeeTier != null) {
await prefs.setInt('current_fee_priority', activeFeeTier);
}
//translate current balance mode
final currentBalanceMode = await ios_legacy_helper.getInt('display_balance_mode');
if (currentBalanceMode != null) {
await prefs.setInt('current_balance_display_mode', currentBalanceMode);
}
//translate should save recipient address
final shouldSave = await ios_legacy_helper.getBool('should_save_recipient_address');
if (shouldSave != null) {
await prefs.setBool('save_recipient_address', shouldSave);
}
//translate biometric
final biometricOn = await ios_legacy_helper.getBool('biometric_authentication_on');
if (biometricOn != null) {
await prefs.setBool('allow_biometrical_authentication', biometricOn);
}
//read the current theme as integer, write it back as a bool
final currentTheme = prefs.getInt('current-theme');
bool isDark = false;
if (currentTheme == 1) {
isDark = true;
}
await prefs.setBool('dark_theme', isDark);
//assign the pin length
final pinLength = await ios_legacy_helper.getInt('pin-length');
if (pinLength != null) {
await prefs.setInt(PreferencesKey.currentPinLength, pinLength);
}
//default value for display list key?
final walletName = await ios_legacy_helper.getString('current_wallet_name');
if (walletName != null) {
await prefs.setString('current_wallet_name', walletName);
}
await prefs.setInt('current_wallet_type', serializeToInt(WalletType.monero));
await prefs.setBool('ios_migration_user_defaults_completed', true);
}
Future<void> ios_migrate_pin() async {
final prefs = await SharedPreferences.getInstance();
if (prefs.getBool('ios_migration_pin_completed') ?? false) {
return;
}
final flutterSecureStorage = secureStorageShared;
final pinPassword = await flutterSecureStorage.readNoIOptions(key: 'pin_password');
// No pin
if (pinPassword == null) {
await prefs.setBool('ios_migration_pin_completed', true);
return;
}
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
final encodedPassword = encodedPinCode(pin: pinPassword);
await flutterSecureStorage.write(key: key, value: encodedPassword);
await prefs.setBool('ios_migration_pin_completed', true);
}
Future<void> ios_migrate_wallet_passwords() async {
final prefs = await SharedPreferences.getInstance();
if (prefs.getBool('ios_migration_wallet_passwords_completed') ?? false) {
return;
}
final appDocDir = await getApplicationDocumentsDirectory();
final flutterSecureStorage = secureStorageShared;
final keyService = KeyService(flutterSecureStorage);
final walletsDir = Directory('${appDocDir.path}/wallets');
final moneroWalletsDir = Directory('${walletsDir.path}/monero');
if (!moneroWalletsDir.existsSync() || moneroWalletsDir.listSync().isEmpty) {
await prefs.setBool('ios_migration_wallet_passwords_completed', true);
return;
}
moneroWalletsDir.listSync().forEach((item) async {
try {
if (item is Directory) {
final name = item.path.split('/').last;
final oldKey = 'wallet_monero_' + name + '_password';
final password = await flutterSecureStorage.readNoIOptions(key: oldKey);
await keyService.saveWalletPassword(walletName: name, password: password!);
}
} catch (e) {
printV(e.toString());
}
});
await prefs.setBool('ios_migration_wallet_passwords_completed', true);
}
FiatCurrency convertFiatLegacy(int raw) {
final _map = {
0: 'aud',
1: 'bgn',
2: 'brl',
3: 'cad',
4: 'chf',
5: 'cny',
6: 'czk',
7: 'eur',
8: 'dkk',
9: 'gbp',
10: 'hkd',
11: 'hrk',
12: 'huf',
13: 'idr',
14: 'ils',
15: 'inr',
16: 'isk',
17: 'jpy',
18: 'krw',
19: 'mxn',
20: 'myr',
21: 'nok',
22: 'nzd',
23: 'php',
24: 'pln',
25: 'ron',
26: 'rub',
27: 'sek',
28: 'sgd',
29: 'thb',
30: 'try',
31: 'usd',
32: 'zar',
33: 'vef'
};
final fiatAsString = _map[raw]!;
return FiatCurrency.deserialize(raw: fiatAsString.toUpperCase());
}
Future<void> android_migrate_hives({required Directory appDocDir}) async {
final dbDir = Directory('${appDocDir.path}/db');
final files = <File>[];
appDocDir.listSync().forEach((FileSystemEntity item) {
final ext = item.path.split('.').last;
if (item is File && (ext == "hive" || ext == "lock")) {
files.add(item);
}
});
if (!dbDir.existsSync()) {
dbDir.createSync();
}
files.forEach((File hive) {
final name = hive.path.split('/').last;
hive.copySync('${dbDir.path}/$name');
hive.deleteSync();
});
}
Future<void> android_migrate_wallets({required Directory appDocDir}) async {
final walletsDir = Directory('${appDocDir.path}/wallets');
final moneroWalletsDir = Directory('${walletsDir.path}/monero');
final dirs = <Directory>[];
appDocDir.listSync().forEach((FileSystemEntity item) {
final name = item.path.split('/').last;
if (item is Directory && !reservedNames.contains(name)) {
dirs.add(item);
}
});
if (!moneroWalletsDir.existsSync()) {
await moneroWalletsDir.create(recursive: true);
}
dirs.forEach((Directory dir) {
final name = dir.path.split('/').last;
final newDir = Directory('${moneroWalletsDir.path}/$name');
newDir.createSync();
dir.listSync().forEach((file) {
if (file is File) {
final fileName = file.path.split('/').last;
file.copySync('${newDir.path}/$fileName');
file.deleteSync();
}
});
dir.deleteSync();
});
}
Future<void> ios_migrate_wallet_info(Box<WalletInfo> walletsInfoSource) async {
final prefs = await SharedPreferences.getInstance();
if (prefs.getBool('ios_migration_wallet_info_completed') ?? false) {
return;
}
try {
final appDocDir = await getApplicationDocumentsDirectory();
final walletsDir = Directory('${appDocDir.path}/wallets');
final moneroWalletsDir = Directory('${walletsDir.path}/monero');
final infoRecords = moneroWalletsDir
.listSync()
.map((item) {
try {
if (item is Directory) {
final name = item.path.split('/').last;
final configFile = File('${item.path}/$name.json');
if (!configFile.existsSync()) {
return null;
}
final config = json.decode(configFile.readAsStringSync()) as Map<String, dynamic>;
final isRecovery = config['isRecovery'] as bool? ?? false;
final dateAsDouble = config['date'] as double;
final timestamp = dateAsDouble.toInt() * 1000;
final date = DateTime.fromMillisecondsSinceEpoch(timestamp);
final id = walletTypeToString(WalletType.monero).toLowerCase() + '_' + name;
final exist = walletsInfoSource.values.firstWhereOrNull((el) => el.id == id) != null;
if (exist) {
return null;
}
final walletInfo = WalletInfo.external(
id: id,
type: WalletType.monero,
name: name,
isRecovery: isRecovery,
restoreHeight: 0,
date: date,
dirPath: item.path,
path: '${item.path}/$name',
address: '');
return walletInfo;
}
} catch (e) {
printV(e.toString());
return null;
}
})
.where((el) => el != null)
.whereType<WalletInfo>()
.toList();
await walletsInfoSource.addAll(infoRecords);
await prefs.setBool('ios_migration_wallet_info_completed', true);
} catch (e) {
printV(e.toString());
}
}
Future<void> ios_migrate_trades_list(Box<Trade> tradeSource) async {
final prefs = await SharedPreferences.getInstance();
if (prefs.getBool('ios_migration_trade_list_completed') ?? false) {
return;
}
try {
final appDocDir = await getApplicationDocumentsDirectory();
final url = '${appDocDir.path}/trades_list.json';
final file = File(url);
if (!file.existsSync()) {
await prefs.setBool('ios_migration_trade_list_completed', true);
return;
}
final content = file.readAsBytesSync();
final flutterSecureStorage = secureStorageShared;
final masterPassword = await flutterSecureStorage.readNoIOptions(key: 'master_password');
final key = masterPassword!.replaceAll('-', '');
final decoded = await ios_legacy_helper.decrypt(content, key: key, salt: secrets.salt);
final decodedJson = json.decode(decoded) as List<dynamic>;
final trades = decodedJson.map((dynamic el) {
final elAsMap = el as Map<String, dynamic>;
final providerAsString = elAsMap['provider'] as String;
final fromAsString = elAsMap['from'] as String;
final toAsString = elAsMap['to'] as String;
final dateAsDouble = elAsMap['date'] as double;
final tradeId = elAsMap['tradeID'] as String;
final to = CryptoCurrency.fromString(toAsString);
final from = CryptoCurrency.fromString(fromAsString);
final timestamp = dateAsDouble.toInt() * 1000;
final date = DateTime.fromMillisecondsSinceEpoch(timestamp);
ExchangeProviderDescription? provider;
switch (providerAsString.toLowerCase()) {
case 'changenow':
provider = ExchangeProviderDescription.changeNow;
break;
case 'xmr.to':
provider = ExchangeProviderDescription.xmrto;
break;
case 'morph':
provider = ExchangeProviderDescription.morphToken;
break;
default:
break;
}
return Trade(
id: tradeId,
provider: provider!,
from: from,
to: to,
createdAt: date,
amount: '',
receiveAmount: '',
);
});
await tradeSource.addAll(trades);
await prefs.setBool('ios_migration_trade_list_completed', true);
} catch (e) {
printV(e.toString());
}
}
Future<void> ios_migrate_address_book(Box<Contact> contactSource) async {
try {
final prefs = await SharedPreferences.getInstance();
if (prefs.getBool('ios_migration_address_book_completed') ?? false) {
return;
}
final appDocDir = await getApplicationDocumentsDirectory();
final addressBookJSON = File('${appDocDir.path}/address_book.json');
if (!addressBookJSON.existsSync()) {
await prefs.setBool('ios_migration_address_book_completed', true);
return;
}
final List<dynamic> addresses =
json.decode(addressBookJSON.readAsStringSync()) as List<dynamic>;
final contacts = addresses.map((dynamic item) {
final _item = item as Map<String, dynamic>;
final type = _item["type"] as String;
final address = _item["address"] as String;
final name = _item["name"] as String;
return Contact(address: address, name: name, type: CryptoCurrency.fromString(type));
});
await contactSource.addAll(contacts);
await prefs.setBool('ios_migration_address_book_completed', true);
} catch (e) {
printV(e.toString());
}
}