2024-04-30 23:38:49 +08:00
|
|
|
import 'dart:async';
|
|
|
|
|
import 'dart:io';
|
|
|
|
|
|
2025-05-02 02:24:12 +08:00
|
|
|
import 'package:fl_clash/common/common.dart';
|
2026-09-05 20:09:39 +08:00
|
|
|
import 'package:flutter/foundation.dart';
|
2024-04-30 23:38:49 +08:00
|
|
|
import 'package:path/path.dart';
|
|
|
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
|
|
|
|
|
|
class AppPath {
|
|
|
|
|
static AppPath? _instance;
|
2026-10-13 17:01:19 +08:00
|
|
|
late ResolveOnce<Directory> _dataDir;
|
|
|
|
|
late ResolveOnce<Directory?> _downloadDir;
|
|
|
|
|
late ResolveOnce<Directory> _tempDir;
|
2025-07-31 17:09:18 +08:00
|
|
|
Completer<Directory> cacheDir = Completer();
|
2024-06-19 13:13:31 +08:00
|
|
|
|
2026-09-05 20:09:39 +08:00
|
|
|
@visibleForTesting
|
|
|
|
|
static Future<Directory> Function() supportDirectory =
|
|
|
|
|
getApplicationSupportDirectory;
|
|
|
|
|
|
|
|
|
|
@visibleForTesting
|
|
|
|
|
static Future<Directory> Function() temporaryDirectory =
|
|
|
|
|
getTemporaryDirectory;
|
|
|
|
|
|
|
|
|
|
@visibleForTesting
|
|
|
|
|
static Future<Directory> Function() cacheDirectory =
|
|
|
|
|
getApplicationCacheDirectory;
|
|
|
|
|
|
|
|
|
|
@visibleForTesting
|
|
|
|
|
static Future<Directory?> Function() downloadDirectory =
|
|
|
|
|
getDownloadsDirectory;
|
|
|
|
|
|
2024-04-30 23:38:49 +08:00
|
|
|
AppPath._internal() {
|
2026-10-13 17:01:19 +08:00
|
|
|
_bind();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@visibleForTesting
|
|
|
|
|
Future<void> rebind() async {
|
|
|
|
|
cacheDir = Completer();
|
|
|
|
|
_bind();
|
|
|
|
|
await Future.wait([_dataDir.value, _downloadDir.value, _tempDir.value]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _bind() {
|
|
|
|
|
_dataDir = ResolveOnce(supportDirectory);
|
|
|
|
|
_downloadDir = ResolveOnce(downloadDirectory);
|
|
|
|
|
_tempDir = ResolveOnce(temporaryDirectory);
|
|
|
|
|
cacheDirectory().then(cacheDir.complete);
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
factory AppPath() {
|
|
|
|
|
_instance ??= AppPath._internal();
|
|
|
|
|
return _instance!;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-03 21:47:12 +08:00
|
|
|
String get executableExtension {
|
2025-06-07 01:48:34 +08:00
|
|
|
return system.isWindows ? '.exe' : '';
|
2024-12-03 21:47:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String get executableDirPath {
|
|
|
|
|
final currentExecutablePath = Platform.resolvedExecutable;
|
|
|
|
|
return dirname(currentExecutablePath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String get corePath {
|
2025-06-07 01:48:34 +08:00
|
|
|
return join(executableDirPath, 'FlClashCore$executableExtension');
|
2024-12-03 21:47:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String get helperPath {
|
2025-06-07 01:48:34 +08:00
|
|
|
return join(executableDirPath, '$appHelperService$executableExtension');
|
2024-12-03 21:47:12 +08:00
|
|
|
}
|
|
|
|
|
|
2025-02-03 23:32:00 +08:00
|
|
|
Future<String> get downloadDirPath async {
|
2026-10-13 17:01:19 +08:00
|
|
|
final directory = await _downloadDir.value;
|
2026-08-05 10:57:11 +08:00
|
|
|
return directory?.path ?? await homeDirPath;
|
2024-08-04 08:21:14 +08:00
|
|
|
}
|
|
|
|
|
|
2025-02-03 23:32:00 +08:00
|
|
|
Future<String> get homeDirPath async {
|
2026-10-13 17:01:19 +08:00
|
|
|
final directory = await _dataDir.value;
|
2024-04-30 23:38:49 +08:00
|
|
|
return directory.path;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 11:23:09 +08:00
|
|
|
Future<String> get databasePath async {
|
|
|
|
|
final mHomeDirPath = await homeDirPath;
|
|
|
|
|
return join(mHomeDirPath, 'database.sqlite');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<String> get tempFilePath async {
|
2026-10-13 17:01:19 +08:00
|
|
|
final mTempDir = await _tempDir.value;
|
2026-09-05 20:09:39 +08:00
|
|
|
return join(mTempDir.path, 'temp$uniqueId');
|
2025-12-16 11:23:09 +08:00
|
|
|
}
|
|
|
|
|
|
2025-02-03 23:32:00 +08:00
|
|
|
Future<String> get lockFilePath async {
|
2025-07-31 17:09:18 +08:00
|
|
|
final homeDirPath = await appPath.homeDirPath;
|
|
|
|
|
return join(homeDirPath, 'FlClash.lock');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<String> get configFilePath async {
|
2025-12-16 11:23:09 +08:00
|
|
|
final mHomeDirPath = await homeDirPath;
|
|
|
|
|
return join(mHomeDirPath, 'config.yaml');
|
2024-12-03 21:47:12 +08:00
|
|
|
}
|
|
|
|
|
|
2025-02-03 23:32:00 +08:00
|
|
|
Future<String> get sharedPreferencesPath async {
|
2026-10-13 17:01:19 +08:00
|
|
|
final directory = await _dataDir.value;
|
2025-06-07 01:48:34 +08:00
|
|
|
return join(directory.path, 'shared_preferences.json');
|
2025-02-03 23:32:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<String> get profilesPath async {
|
2026-10-13 17:01:19 +08:00
|
|
|
final directory = await _dataDir.value;
|
2024-05-20 15:15:09 +08:00
|
|
|
return join(directory.path, profilesDirectoryName);
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
2025-12-16 11:23:09 +08:00
|
|
|
Future<String> getProfilePath(String fileName) async {
|
|
|
|
|
return join(await profilesPath, '$fileName.yaml');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<String> get scriptsDirPath async {
|
|
|
|
|
final path = await homeDirPath;
|
|
|
|
|
return join(path, 'scripts');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<String> getScriptPath(String fileName) async {
|
|
|
|
|
final path = await scriptsDirPath;
|
|
|
|
|
return join(path, '$fileName.js');
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
2024-09-26 14:29:04 +08:00
|
|
|
|
2026-09-25 03:56:59 +08:00
|
|
|
Future<String> get providerCacheRootPath async {
|
|
|
|
|
final directory = await homeDirPath;
|
|
|
|
|
return join(directory, providersDirectoryName);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-08 17:58:43 +08:00
|
|
|
Future<String> getProviderCachePath(String fileName) async {
|
2026-09-25 03:56:59 +08:00
|
|
|
return join(
|
|
|
|
|
await providerCacheRootPath,
|
2026-10-08 17:58:43 +08:00
|
|
|
rulesProviderDirectoryName,
|
2026-09-25 03:56:59 +08:00
|
|
|
fileName,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
Future<String> getProvidersRootPath() async {
|
|
|
|
|
final directory = await profilesPath;
|
2026-09-05 20:09:39 +08:00
|
|
|
return join(directory, providersDirectoryName);
|
2025-07-31 17:09:18 +08:00
|
|
|
}
|
|
|
|
|
|
2026-09-05 20:09:39 +08:00
|
|
|
Future<String> getProviderDirPath(int profileId, String type) async {
|
|
|
|
|
final directory = await getProvidersRootPath();
|
|
|
|
|
return join(directory, profileId.toString(), type);
|
2025-05-02 02:24:12 +08:00
|
|
|
}
|
|
|
|
|
|
2026-09-05 20:09:39 +08:00
|
|
|
Future<void> ensureProviderDirs(int profileId) async {
|
|
|
|
|
for (final type in const [
|
|
|
|
|
proxiesProviderDirectoryName,
|
|
|
|
|
rulesProviderDirectoryName,
|
|
|
|
|
]) {
|
|
|
|
|
final directory = Directory(await getProviderDirPath(profileId, type));
|
|
|
|
|
if (await directory.exists()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
await directory.create(recursive: true);
|
|
|
|
|
}
|
2024-12-03 21:47:12 +08:00
|
|
|
}
|
|
|
|
|
|
2024-09-26 14:29:04 +08:00
|
|
|
Future<String> get tempPath async {
|
2026-10-13 17:01:19 +08:00
|
|
|
final directory = await _tempDir.value;
|
2024-09-26 14:29:04 +08:00
|
|
|
return directory.path;
|
|
|
|
|
}
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final appPath = AppPath();
|
2026-09-05 20:09:39 +08:00
|
|
|
|
|
|
|
|
String getBackupFileName() {
|
|
|
|
|
return '${appName}_backup_${DateTime.now().show}.zip';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String get logFileName {
|
|
|
|
|
return '${appName}_${DateTime.now().show}.log';
|
|
|
|
|
}
|