| 1 | import 'dart:io'; |
| 2 | import 'package:cw_core/utils/print_verbose.dart'; |
| 3 | import 'package:path_provider/path_provider.dart'; |
| 4 | import 'package:path/path.dart' as p; |
| 5 | |
| 6 | String? _rootDirPath; |
| 7 | |
| 8 | const String _tailsData = '/live/persistence/TailsData_unlocked/Persistent'; |
| 9 | |
| 10 | bool get isNonAmnesticTails { |
| 11 | try { |
| 12 | final os = File("/etc/os-release").readAsLinesSync(); |
| 13 | for (var line in os) { |
| 14 | if (!line.startsWith("ID=")) continue; |
| 15 | if (!line.contains("tails")) continue; |
| 16 | return Directory(_tailsData).existsSync(); |
| 17 | } |
| 18 | } catch (e) { |
| 19 | return false; |
| 20 | } |
| 21 | return false; |
| 22 | } |
| 23 | |
| 24 | bool showNotice = true; |
| 25 | |
| 26 | void setRootDirFromEnv() => _rootDirPath = Platform.environment['CAKE_WALLET_DIR']; |
| 27 | |
| 28 | void copyDirectory(Directory source, Directory destination) { |
| 29 | source.listSync(recursive: false).forEach((var entity) { |
| 30 | if (entity is Directory) { |
| 31 | var newDirectory = Directory(p.join(destination.absolute.path, p.basename(entity.path))); |
| 32 | newDirectory.createSync(recursive: true); |
| 33 | copyDirectory(entity.absolute, newDirectory); |
| 34 | } else if (entity is File) { |
| 35 | destination.createSync(recursive: true); |
| 36 | entity.copySync(p.join(destination.path, p.basename(entity.path))); |
| 37 | } |
| 38 | }); |
| 39 | } |
| 40 | |
| 41 | Future<void> linuxSymlinkSharedPreferences() async { |
| 42 | if (!Platform.isLinux) return; // nuh-uh |
| 43 | final dataHome = Platform.environment["XDG_DATA_HOME"] ?? |
| 44 | p.join(Platform.environment["HOME"] ?? "", ".local", "share"); |
| 45 | var cakeNames = ['com.example.cake_wallet', 'cake_wallet']; |
| 46 | for (String name in cakeNames) { |
| 47 | final oldPath = p.join(dataHome, name); |
| 48 | final newPath = p.join((await getAppDir()).path, "_local_share"); |
| 49 | final oldDir = Directory(oldPath); |
| 50 | final oldLink = Link(oldPath); |
| 51 | final newDir = Directory(newPath); |
| 52 | if (oldDir.existsSync()) { |
| 53 | if (oldLink.existsSync()) { |
| 54 | printV("not creating, link exists"); |
| 55 | } else { |
| 56 | if (newDir.existsSync()) { |
| 57 | newDir.renameSync("${newPath}_${DateTime.now().millisecondsSinceEpoch ~/ 1000}"); |
| 58 | } |
| 59 | copyDirectory(oldDir, newDir); |
| 60 | oldDir.deleteSync(recursive: true); |
| 61 | } |
| 62 | } |
| 63 | if (!oldLink.existsSync()) { |
| 64 | oldLink.create(newPath, recursive: true); |
| 65 | } |
| 66 | if (!newDir.existsSync()) { |
| 67 | newDir.createSync(recursive: true); |
| 68 | } |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | Future<Directory> getAppDir() async { |
| 73 | const String appName = 'cake_wallet'; |
| 74 | Directory dir; |
| 75 | |
| 76 | if (_rootDirPath != null && _rootDirPath!.isNotEmpty) { |
| 77 | dir = Directory.fromUri(Uri.file(_rootDirPath!)); |
| 78 | dir.create(recursive: true); |
| 79 | } else { |
| 80 | if (Platform.isWindows) { |
| 81 | dir = await getApplicationSupportDirectory(); |
| 82 | } else if (Platform.isLinux) { |
| 83 | String? appDirPath; |
| 84 | try { |
| 85 | dir = await getApplicationDocumentsDirectory(); |
| 86 | appDirPath = '${dir.path}/$appName'; |
| 87 | } catch (e) { |
| 88 | appDirPath = null; |
| 89 | } |
| 90 | // App will try to use last entry in here, so {distro,package}-specific paths can be |
| 91 | // be put as one of last items (tails - I'm looking at you), and other paths can be |
| 92 | // added in the order of preference |
| 93 | // Which currently is $HOME/.config/$appName - as this is the most standard directory |
| 94 | // for storing things that users in general back-up |
| 95 | var linuxAppPath = [ |
| 96 | if (appDirPath != null) appDirPath, // old preferred |
| 97 | p.join('/home', Platform.environment['USER'] ?? "null", appName), // old fallback |
| 98 | if (Platform.environment['HOME'] != null) |
| 99 | p.join(Platform.environment['HOME']!, ".$appName"), // old fallback but using HOME |
| 100 | if (Platform.environment['HOME'] != null) |
| 101 | p.join(Platform.environment['HOME']!, '.config', appName), // old fallback but using HOME |
| 102 | if (isNonAmnesticTails) p.join(_tailsData, ".$appName") // tails (if persistance is enabled) |
| 103 | ]; |
| 104 | |
| 105 | String preferredPath = linuxAppPath.last; |
| 106 | |
| 107 | preferredLoop: |
| 108 | for (String notSoPreferredPath in linuxAppPath) { |
| 109 | if (notSoPreferredPath == linuxAppPath.last) continue; |
| 110 | bool useThisOne = Directory(notSoPreferredPath).existsSync(); |
| 111 | if (useThisOne) { |
| 112 | if (showNotice) { |
| 113 | showNotice = false; |
| 114 | printV( |
| 115 | "Not using $preferredPath because $notSoPreferredPath exists, falling back for backwards compatibility"); |
| 116 | printV( |
| 117 | "Can't see your wallet? Check\n - ${linuxAppPath.join("\n - ")}\n and move directory that to $preferredPath"); |
| 118 | printV("Or use CAKE_WALLET_DIR=/path/to/app/ ${Platform.executable}"); |
| 119 | } |
| 120 | preferredPath = notSoPreferredPath; |
| 121 | break preferredLoop; |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | dir = Directory.fromUri(Uri.file(preferredPath)); |
| 126 | await dir.create(recursive: true); |
| 127 | } else { |
| 128 | dir = await getApplicationDocumentsDirectory(); |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | return dir; |
| 133 | } |