import 'dart:async'; import 'dart:io'; import 'package:fl_clash/common/common.dart'; import 'package:flutter/foundation.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; class AppPath { static AppPath? _instance; late ResolveOnce _dataDir; late ResolveOnce _downloadDir; late ResolveOnce _tempDir; Completer cacheDir = Completer(); @visibleForTesting static Future Function() supportDirectory = getApplicationSupportDirectory; @visibleForTesting static Future Function() temporaryDirectory = getTemporaryDirectory; @visibleForTesting static Future Function() cacheDirectory = getApplicationCacheDirectory; @visibleForTesting static Future Function() downloadDirectory = getDownloadsDirectory; AppPath._internal() { _bind(); } @visibleForTesting Future 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); } factory AppPath() { _instance ??= AppPath._internal(); return _instance!; } String get executableExtension { return system.isWindows ? '.exe' : ''; } String get executableDirPath { final currentExecutablePath = Platform.resolvedExecutable; return dirname(currentExecutablePath); } String get corePath { return join(executableDirPath, 'FlClashCore$executableExtension'); } String get helperPath { return join(executableDirPath, '$appHelperService$executableExtension'); } Future get downloadDirPath async { final directory = await _downloadDir.value; return directory?.path ?? await homeDirPath; } Future get homeDirPath async { final directory = await _dataDir.value; return directory.path; } Future get databasePath async { final mHomeDirPath = await homeDirPath; return join(mHomeDirPath, 'database.sqlite'); } Future get tempFilePath async { final mTempDir = await _tempDir.value; return join(mTempDir.path, 'temp$uniqueId'); } Future get lockFilePath async { final homeDirPath = await appPath.homeDirPath; return join(homeDirPath, 'FlClash.lock'); } Future get configFilePath async { final mHomeDirPath = await homeDirPath; return join(mHomeDirPath, 'config.yaml'); } Future get sharedPreferencesPath async { final directory = await _dataDir.value; return join(directory.path, 'shared_preferences.json'); } Future get profilesPath async { final directory = await _dataDir.value; return join(directory.path, profilesDirectoryName); } Future getProfilePath(String fileName) async { return join(await profilesPath, '$fileName.yaml'); } Future get scriptsDirPath async { final path = await homeDirPath; return join(path, 'scripts'); } Future getScriptPath(String fileName) async { final path = await scriptsDirPath; return join(path, '$fileName.js'); } Future get providerCacheRootPath async { final directory = await homeDirPath; return join(directory, providersDirectoryName); } Future getProviderCachePath(String fileName) async { return join( await providerCacheRootPath, rulesProviderDirectoryName, fileName, ); } Future getProvidersRootPath() async { final directory = await profilesPath; return join(directory, providersDirectoryName); } Future getProviderDirPath(int profileId, String type) async { final directory = await getProvidersRootPath(); return join(directory, profileId.toString(), type); } Future 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); } } Future get tempPath async { final directory = await _tempDir.value; return directory.path; } } final appPath = AppPath(); String getBackupFileName() { return '${appName}_backup_${DateTime.now().show}.zip'; } String get logFileName { return '${appName}_${DateTime.now().show}.log'; }