diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 7a0fecd3e..dc611d500 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -5,6 +5,7 @@ import 'dart:isolate'; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cw_core/hardware/hardware_wallet_service.dart'; +import 'package:cw_core/root_dir.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_core/utils/print_verbose.dart'; @@ -374,7 +375,7 @@ abstract class ElectrumWalletBase runningIsolate.kill(priority: Isolate.immediate); } - final appDir = await getApplicationSupportDirectory(); + final appDir = await getAppDir(); String debugLogPath = "${appDir.path}/logs/debug.log"; final receivePort = ReceivePort(); diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 681e68d47..c4b45c776 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -1084,6 +1084,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.9.0" + sqlite3_flutter_libs: + dependency: transitive + description: + name: sqlite3_flutter_libs + sha256: "69c80d812ef2500202ebd22002cbfc1b6565e9ff56b2f971e757fac5d42294df" + url: "https://pub.dev" + source: hosted + version: "0.5.40" stack_trace: dependency: transitive description: diff --git a/cw_core/lib/db/sqlite.dart b/cw_core/lib/db/sqlite.dart index e8bba6689..fdd589210 100644 --- a/cw_core/lib/db/sqlite.dart +++ b/cw_core/lib/db/sqlite.dart @@ -1,9 +1,14 @@ -import 'package:sqflite/sqflite.dart'; +import 'dart:io'; + +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; late Database db; Future initDb({String? pathOverride}) async { + if (Platform.isLinux || Platform.isWindows) { + databaseFactory = databaseFactoryFfi; + } db = await openDatabase( pathOverride ?? "cake.db", version: 1, diff --git a/cw_core/lib/root_dir.dart b/cw_core/lib/root_dir.dart index c2a8170bc..7f75b20f4 100644 --- a/cw_core/lib/root_dir.dart +++ b/cw_core/lib/root_dir.dart @@ -1,11 +1,76 @@ import 'dart:io'; +import 'package:cw_core/utils/print_verbose.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as p; String? _rootDirPath; -void setRootDirFromEnv() => _rootDirPath = Platform.environment['CAKE_WALLET_DIR']; +const String _tailsData = '/live/persistence/TailsData_unlocked/Persistent'; -Future getAppDir({String appName = 'cake_wallet'}) async { +bool get isNonAmnesticTails { + try { + final os = File("/etc/os-release").readAsLinesSync(); + for (var line in os) { + if (!line.startsWith("ID=")) continue; + if (!line.contains("tails")) continue; + return Directory(_tailsData).existsSync(); + } + } catch (e) { + return false; + } + return false; +} + +bool showNotice = true; + +void setRootDirFromEnv() => + _rootDirPath = Platform.environment['CAKE_WALLET_DIR']; + +void copyDirectory(Directory source, Directory destination) { + source.listSync(recursive: false).forEach((var entity) { + if (entity is Directory) { + var newDirectory = Directory(p.join(destination.absolute.path, p.basename(entity.path))); + newDirectory.createSync(recursive: true); + copyDirectory(entity.absolute, newDirectory); + } else if (entity is File) { + destination.createSync(recursive: true); + entity.copySync(p.join(destination.path, p.basename(entity.path))); + } + }); +} + +Future linuxSymlinkSharedPreferences() async { + if (!Platform.isLinux) return; // nuh-uh + final dataHome = Platform.environment["XDG_DATA_HOME"] ?? p.join(Platform.environment["HOME"] ?? "", ".local", "share"); + var cakeNames = ['com.example.cake_wallet', 'cake_wallet']; + for (String name in cakeNames) { + final oldPath = p.join(dataHome, name); + final newPath = p.join((await getAppDir()).path, "_local_share"); + final oldDir = Directory(oldPath); + final oldLink = Link(oldPath); + final newDir = Directory(newPath); + if (oldDir.existsSync()) { + if (oldLink.existsSync()) { + printV("not creating, link exists"); + } else { + if (newDir.existsSync()) { + newDir.renameSync("${newPath}_${DateTime.now().millisecondsSinceEpoch~/1000}"); + } + copyDirectory(oldDir, newDir); + oldDir.deleteSync(recursive: true); + } + } + if (!oldLink.existsSync()) { + oldLink.create(newPath, recursive: true); + } + if (!newDir.existsSync()) { + newDir.createSync(recursive: true); + } + } +} + +Future getAppDir() async { + const String appName = 'cake_wallet'; Directory dir; if (_rootDirPath != null && _rootDirPath!.isNotEmpty) { @@ -15,16 +80,45 @@ Future getAppDir({String appName = 'cake_wallet'}) async { if (Platform.isWindows) { dir = await getApplicationSupportDirectory(); } else if (Platform.isLinux) { - String appDirPath; - + String? appDirPath; try { dir = await getApplicationDocumentsDirectory(); appDirPath = '${dir.path}/$appName'; } catch (e) { - appDirPath = '/home/${Platform.environment['USER']}/.$appName'; + appDirPath = null; + } + // App will try to use last entry in here, so {distro,package}-specific paths can be + // be put as one of last items (tails - I'm looking at you), and other paths can be + // added in the order of preference + // Which currently is $HOME/.config/$appName - as this is the most standard directory + // for storing things that users in general back-up + var linuxAppPath = [ + if (appDirPath != null) appDirPath, // old preferred + p.join('/home', Platform.environment['USER']??"null", appName), // old fallback + if (Platform.environment['HOME'] != null) p.join(Platform.environment['HOME']!, ".$appName"), // old fallback but using HOME + if (Platform.environment['HOME'] != null) p.join(Platform.environment['HOME']!, '.config', appName), // old fallback but using HOME + if (isNonAmnesticTails) p.join(_tailsData, ".$appName") // tails (if persistance is enabled) + ]; + + String preferredPath = linuxAppPath.last; + + preferredLoop: + for (String notSoPreferredPath in linuxAppPath) { + if (notSoPreferredPath == linuxAppPath.last) continue; + bool useThisOne = Directory(notSoPreferredPath).existsSync(); + if (useThisOne) { + if (showNotice) { + showNotice = false; + printV("Not using $preferredPath because $notSoPreferredPath exists, falling back for backwards compatibility"); + printV("Can't see your wallet? Check\n - ${linuxAppPath.join("\n - ")}\n and move directory that to $preferredPath"); + printV("Or use CAKE_WALLET_DIR=/path/to/app/ ${Platform.executable}"); + } + preferredPath = notSoPreferredPath; + break preferredLoop; + } } - dir = Directory.fromUri(Uri.file(appDirPath)); + dir = Directory.fromUri(Uri.file(preferredPath)); await dir.create(recursive: true); } else { dir = await getApplicationDocumentsDirectory(); diff --git a/cw_core/lib/utils/tor/abstract.dart b/cw_core/lib/utils/tor/abstract.dart index 6fc4016a3..70272e47a 100644 --- a/cw_core/lib/utils/tor/abstract.dart +++ b/cw_core/lib/utils/tor/abstract.dart @@ -31,16 +31,17 @@ abstract class CakeTorInstance { final uri = Uri.tryParse(socksServer); if (uri != null) { return CakeTorSocks(uri.port); + } else { + final uri = Uri.tryParse("socks5://$socksServer"); + if (uri != null) { + return CakeTorSocks(uri.port); + } } } - final os = File("/etc/os-release").readAsLinesSync(); - for (var line in os) { - if (!line.startsWith("ID=")) continue; - if (!line.contains("tails")) continue; - return CakeTorSocks(9150); - } } catch (e) { - printV("Failed to identify linux version - /etc/os-release missing"); + printV( + "Failed to identify linux version - no SOCKS_SERVER variable found or malformed", + ); } } try { diff --git a/cw_core/pubspec.lock b/cw_core/pubspec.lock index 918a81a0b..dd5dbaacf 100644 --- a/cw_core/pubspec.lock +++ b/cw_core/pubspec.lock @@ -730,6 +730,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.9.0" + sqlite3_flutter_libs: + dependency: "direct main" + description: + name: sqlite3_flutter_libs + sha256: "69c80d812ef2500202ebd22002cbfc1b6565e9ff56b2f971e757fac5d42294df" + url: "https://pub.dev" + source: hosted + version: "0.5.40" stack_trace: dependency: transitive description: diff --git a/cw_core/pubspec.yaml b/cw_core/pubspec.yaml index 7c963f246..4dc27a062 100644 --- a/cw_core/pubspec.yaml +++ b/cw_core/pubspec.yaml @@ -46,6 +46,7 @@ dependencies: ref: cake-update-v2 sqflite: ^2.4.1 sqflite_common_ffi: ^2.3.4+4 + sqlite3_flutter_libs: 0.5.40 dev_dependencies: flutter_test: @@ -63,7 +64,7 @@ dependency_overrides: # The following section is specific to Flutter. flutter: - uses-material-design: true + uses-material-design: true # To add assets to your package, add an assets section, like this: # assets: diff --git a/cw_decred/pubspec.lock b/cw_decred/pubspec.lock index 855857215..ab2826fa2 100644 --- a/cw_decred/pubspec.lock +++ b/cw_decred/pubspec.lock @@ -761,6 +761,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.9.0" + sqlite3_flutter_libs: + dependency: transitive + description: + name: sqlite3_flutter_libs + sha256: "69c80d812ef2500202ebd22002cbfc1b6565e9ff56b2f971e757fac5d42294df" + url: "https://pub.dev" + source: hosted + version: "0.5.40" stack_trace: dependency: transitive description: diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index ae521ab8d..f9ef7a82a 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -874,6 +874,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.9.0" + sqlite3_flutter_libs: + dependency: transitive + description: + name: sqlite3_flutter_libs + sha256: "69c80d812ef2500202ebd22002cbfc1b6565e9ff56b2f971e757fac5d42294df" + url: "https://pub.dev" + source: hosted + version: "0.5.40" stack_trace: dependency: transitive description: diff --git a/cw_monero/test/monero_wallet_service_test.dart b/cw_monero/test/monero_wallet_service_test.dart index 3b24c205b..780caea44 100644 --- a/cw_monero/test/monero_wallet_service_test.dart +++ b/cw_monero/test/monero_wallet_service_test.dart @@ -21,7 +21,6 @@ Future main() async { late File moneroCBinary; setUpAll(() async { - databaseFactory = databaseFactoryFfi; await initDb(pathOverride: './test/data/db'); Hive.init('./test/data/db'); PathProviderPlatform.instance = MockPathProviderPlatform(); diff --git a/cw_nano/pubspec.lock b/cw_nano/pubspec.lock index c009b6502..42df7b8d6 100644 --- a/cw_nano/pubspec.lock +++ b/cw_nano/pubspec.lock @@ -879,6 +879,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.9.0" + sqlite3_flutter_libs: + dependency: transitive + description: + name: sqlite3_flutter_libs + sha256: "69c80d812ef2500202ebd22002cbfc1b6565e9ff56b2f971e757fac5d42294df" + url: "https://pub.dev" + source: hosted + version: "0.5.40" stack_trace: dependency: transitive description: diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock index 2e1b3fb09..ce8192f84 100644 --- a/cw_wownero/pubspec.lock +++ b/cw_wownero/pubspec.lock @@ -778,6 +778,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.9.0" + sqlite3_flutter_libs: + dependency: transitive + description: + name: sqlite3_flutter_libs + sha256: "69c80d812ef2500202ebd22002cbfc1b6565e9ff56b2f971e757fac5d42294df" + url: "https://pub.dev" + source: hosted + version: "0.5.40" stack_trace: dependency: transitive description: diff --git a/cw_zano/pubspec.lock b/cw_zano/pubspec.lock index 0c4a57658..826e385b8 100644 --- a/cw_zano/pubspec.lock +++ b/cw_zano/pubspec.lock @@ -775,6 +775,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.9.0" + sqlite3_flutter_libs: + dependency: transitive + description: + name: sqlite3_flutter_libs + sha256: "69c80d812ef2500202ebd22002cbfc1b6565e9ff56b2f971e757fac5d42294df" + url: "https://pub.dev" + source: hosted + version: "0.5.40" stack_trace: dependency: transitive description: diff --git a/integration_test_runner.sh b/integration_test_runner.sh index cf2501481..de21b3edc 100755 --- a/integration_test_runner.sh +++ b/integration_test_runner.sh @@ -14,6 +14,7 @@ RETRY_COUNT=${RETRY_COUNT:-1} DATA_DIRS=( "$HOME/.local/share/com.example.cake_wallet" "$HOME/Documents/cake_wallet" + "$HOME/.config/cake_wallet" ) # Global state @@ -46,7 +47,7 @@ format_duration() { local hours=$((seconds / 3600)) local minutes=$(((seconds % 3600) / 60)) local secs=$((seconds % 60)) - + if (( hours > 0 )); then echo "${hours}h ${minutes}m ${secs}s" elif (( minutes > 0 )); then @@ -92,7 +93,7 @@ run_test() { local end_time=$(date +%s) local duration=$((end_time - start_time)) test_durations+=("$duration") - + log "āœ… Test passed: $test_name ($(format_duration $duration))" passed_tests+=("$test_name") return 0 @@ -101,9 +102,9 @@ run_test() { local end_time=$(date +%s) local duration=$((end_time - start_time)) test_durations+=("$duration") - + log "āŒ Test failed: $test_name ($(format_duration $duration))" - + if (( retry_count < RETRY_COUNT )); then log "Retrying test: $test_name" retry_count=$((retry_count + 1)) @@ -125,7 +126,7 @@ main() { while IFS= read -r -d $'\0' file; do targets+=("$file") done < <(find integration_test/test_suites -name "*.dart" -type f -print0) - + if [[ $? -ne 0 ]]; then error "Failed to find test files" exit 1 @@ -140,7 +141,7 @@ main() { # Record overall start time local overall_start_time=$(date +%s) - + # Run tests sequentially for target in "${targets[@]}"; do run_test "$target" @@ -156,7 +157,7 @@ main() { echo "Passed: ${#passed_tests[@]}" echo "Failed: ${#failed_tests[@]}" echo "Total duration: $(format_duration $total_duration)" - + if (( ${#passed_tests[@]} > 0 )); then echo -e "\nāœ… Passed Tests:" for i in $(seq 0 $((${#passed_tests[@]} - 1))); do diff --git a/lib/main.dart b/lib/main.dart index fe6e9477a..77430f08f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -96,6 +96,12 @@ Future runAppWithZone({Key? topLevelKey}) async { } catch (e) { printV("Failed to initialize tor: $e"); } + + try { + await linuxSymlinkSharedPreferences(); + } catch (e) { + printV("Failed to symlink linux preferences: $e"); + } await initializeAppAtRoot(); diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart index 655abb5a6..99cecc5dc 100644 --- a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart @@ -48,7 +48,7 @@ class _DesktopWalletSelectionDropDownState extends State Image.asset( 'assets/images/new_wallet.png',