import 'dart:convert'; import 'dart:io'; import 'package:archive/archive_io.dart'; import 'package:drift/drift.dart'; import 'package:drift_flutter/drift_flutter.dart'; import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/database/database.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/models/models.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:path/path.dart'; Future decodeJSONTask(String data) async { return await compute(_decodeJSON, data); } Future _decodeJSON(String content) async { return json.decode(content); } Future encodeJSONTask(T data) async { return await compute(_encodeJSON, data); } Future _encodeJSON(T content) async { return json.encode(content); } Future encodeYamlTask(T data) async { return await compute(_encodeYaml, data); } Future _encodeYaml(T content) async { return yaml.encode(content); } Future> toGroupsTask(ComputeGroupsState data) async { return await compute>(_toGroupsTask, data); } Future> _toGroupsTask(ComputeGroupsState state) async { final proxiesData = state.proxiesData; final all = proxiesData.all; final sortType = state.sortType; final delayMap = state.delayMap; final selectedMap = state.selectedMap; final defaultTestUrl = state.defaultTestUrl; final proxies = proxiesData.proxies; if (proxies.isEmpty) return []; final groupsRaw = all .where((name) { final proxy = proxies[name] ?? {}; return GroupTypeExtension.valueList.contains(proxy['type']); }) .map((groupName) { final group = proxies[groupName]; group['all'] = ((group['all'] ?? []) as List) .map((name) => proxies[name]) .where((proxy) => proxy != null) .toList(); return group; }) .toList(); final groups = groupsRaw.map((e) => Group.fromJson(e)).toList(); return computeSort( groups: groups, sortType: sortType, delayMap: delayMap, selectedMap: selectedMap, defaultTestUrl: defaultTestUrl, ); } Future> makeRealProfileTask( MakeRealProfileState data, ) async { return await compute>( _makeRealProfileTask, data, ); } Future> _makeRealProfileTask( MakeRealProfileState data, ) async { final rawConfig = Map.from(data.rawConfig); final realPatchConfig = data.realPatchConfig; final profilesPath = data.profilesPath; final profileId = data.profileId; final overrideDns = data.overrideDns; final addedRules = data.addedRules; final appendSystemDns = data.appendSystemDns; final defaultUA = data.defaultUA; String getProvidersFilePathInner(String type, String url) { return join( profilesPath, 'providers', profileId.toString(), type, url.toMd5(), ); } rawConfig['external-controller'] = realPatchConfig.externalController.value; rawConfig['external-ui'] = ''; rawConfig['interface-name'] = ''; rawConfig['external-ui-url'] = ''; rawConfig['tcp-concurrent'] = realPatchConfig.tcpConcurrent; rawConfig['unified-delay'] = realPatchConfig.unifiedDelay; rawConfig['ipv6'] = realPatchConfig.ipv6; rawConfig['log-level'] = realPatchConfig.logLevel.name; rawConfig['port'] = 0; rawConfig['socks-port'] = 0; rawConfig['keep-alive-interval'] = realPatchConfig.keepAliveInterval; rawConfig['mixed-port'] = realPatchConfig.mixedPort; rawConfig['port'] = realPatchConfig.port; rawConfig['socks-port'] = realPatchConfig.socksPort; rawConfig['redir-port'] = realPatchConfig.redirPort; rawConfig['tproxy-port'] = realPatchConfig.tproxyPort; rawConfig['find-process-mode'] = realPatchConfig.findProcessMode.name; rawConfig['allow-lan'] = realPatchConfig.allowLan; rawConfig['mode'] = realPatchConfig.mode.name; if (rawConfig['tun'] == null) { rawConfig['tun'] = {}; } rawConfig['tun']['enable'] = realPatchConfig.tun.enable; rawConfig['tun']['device'] = realPatchConfig.tun.device; rawConfig['tun']['dns-hijack'] = realPatchConfig.tun.dnsHijack; rawConfig['tun']['stack'] = realPatchConfig.tun.stack.name; rawConfig['tun']['route-address'] = realPatchConfig.tun.routeAddress; rawConfig['tun']['auto-route'] = realPatchConfig.tun.autoRoute; rawConfig['geodata-loader'] = realPatchConfig.geodataLoader.name; if (rawConfig['sniffer']?['sniff'] != null) { for (final value in (rawConfig['sniffer']?['sniff'] as Map).values) { if (value['ports'] != null && value['ports'] is List) { value['ports'] = value['ports']?.map((item) => item.toString()).toList() ?? []; } } } if (rawConfig['profile'] == null) { rawConfig['profile'] = {}; } if (rawConfig['proxy-providers'] != null) { final proxyProviders = rawConfig['proxy-providers'] as Map; for (final key in proxyProviders.keys) { final proxyProvider = proxyProviders[key]; if (proxyProvider['type'] != 'http') { continue; } if (proxyProvider['url'] != null) { proxyProvider['path'] = getProvidersFilePathInner( 'proxies', proxyProvider['url'], ); } } } if (rawConfig['rule-providers'] != null) { final ruleProviders = rawConfig['rule-providers'] as Map; for (final key in ruleProviders.keys) { final ruleProvider = ruleProviders[key]; if (ruleProvider['type'] != 'http') { continue; } if (ruleProvider['url'] != null) { ruleProvider['path'] = getProvidersFilePathInner( 'rules', ruleProvider['url'], ); } } } rawConfig['profile']['store-selected'] = false; rawConfig['geox-url'] = realPatchConfig.geoXUrl.toJson(); rawConfig['global-ua'] = realPatchConfig.globalUa ?? defaultUA; if (rawConfig['hosts'] == null) { rawConfig['hosts'] = {}; } for (final host in realPatchConfig.hosts.entries) { rawConfig['hosts'][host.key] = host.value.splitByMultipleSeparators; } if (rawConfig['dns'] == null) { rawConfig['dns'] = {}; } final isEnableDns = rawConfig['dns']['enable'] == true; final systemDns = 'system://'; if (overrideDns || !isEnableDns) { final dns = switch (!isEnableDns) { true => realPatchConfig.dns.copyWith( nameserver: [...realPatchConfig.dns.nameserver, systemDns], ), false => realPatchConfig.dns, }; rawConfig['dns'] = dns.toJson(); rawConfig['dns']['nameserver-policy'] = {}; for (final entry in dns.nameserverPolicy.entries) { rawConfig['dns']['nameserver-policy'][entry.key] = entry.value.splitByMultipleSeparators; } } if (appendSystemDns) { final List nameserver = List.from( rawConfig['dns']['nameserver'] ?? [], ); if (!nameserver.contains(systemDns)) { rawConfig['dns']['nameserver'] = [...nameserver, systemDns]; } } List rules = []; if (rawConfig['rules'] != null) { rules = List.from(rawConfig['rules']); } rawConfig.remove('rules'); if (addedRules.isNotEmpty) { final parsedNewRules = addedRules .map((item) => ParsedRule.parseString(item.value)) .toList(); final hasMatchPlaceholder = parsedNewRules.any( (item) => item.ruleTarget?.toUpperCase() == 'MATCH', ); String? replacementTarget; if (hasMatchPlaceholder) { for (int i = rules.length - 1; i >= 0; i--) { final parsed = ParsedRule.parseString(rules[i]); if (parsed.ruleAction == RuleAction.MATCH) { final target = parsed.ruleTarget; if (target != null && target.isNotEmpty) { replacementTarget = target; break; } } } } final List finalAddedRules; if (replacementTarget?.isNotEmpty == true) { finalAddedRules = []; for (int i = 0; i < parsedNewRules.length; i++) { final parsed = parsedNewRules[i]; if (parsed.ruleTarget?.toUpperCase() == 'MATCH') { finalAddedRules.add( parsed.copyWith(ruleTarget: replacementTarget).value, ); } else { finalAddedRules.add(addedRules[i].value); } } } else { finalAddedRules = addedRules.map((e) => e.value).toList(); } rules = [...finalAddedRules, ...rules]; } rawConfig['rules'] = rules; return Map.from(rawConfig); } Future> shakingProfileTask( VM2, Iterable> data, ) async { return await compute< VM3, Iterable, RootIsolateToken>, List >(_shakingProfileTask, VM3(data.a, data.b, RootIsolateToken.instance!)); } Future> _shakingProfileTask( VM3, Iterable, RootIsolateToken> data, ) async { final profileIds = data.a; final scriptIds = data.b; final token = data.c; BackgroundIsolateBinaryMessenger.ensureInitialized(token); final profilesDir = Directory(await appPath.profilesPath); final scriptsDir = Directory(await appPath.scriptsDirPath); final providersDir = Directory(await appPath.getProvidersRootPath()); final List targets = []; void scanDirectory( Directory dir, Iterable baseNames, { bool skipProvidersFolder = false, }) { if (!dir.existsSync()) return; final entities = dir.listSync(recursive: false, followLinks: false); for (final entity in entities) { if (entity is File) { final id = basenameWithoutExtension(entity.path); if (!baseNames.contains(int.tryParse(id))) { targets.add(entity.path); } } else if (skipProvidersFolder && entity is Directory) { if (basename(entity.path) == 'providers') { continue; } } } } scanDirectory(profilesDir, profileIds, skipProvidersFolder: true); scanDirectory(providersDir, profileIds); scanDirectory(scriptsDir, scriptIds); return targets; } Future encodeLogsTask(List data) async { return await compute, String>(_encodeLogsTask, data); } Future _encodeLogsTask(List data) async { final logsRaw = data.map((item) => item.toString()); final logsRawString = logsRaw.join('\n'); return logsRawString; } Future oldToNowTask(Map data) async { final homeDir = await appPath.homeDirPath; return await compute< VM3, String, String>, MigrationData >(_oldToNowTask, VM3(data, homeDir, homeDir)); } Future _oldToNowTask( VM3, String, String> data, ) async { final configMap = data.a; final sourcePath = data.b; final targetPath = data.c; final accessControlMap = configMap['accessControl']; final isAccessControl = configMap['isAccessControl']; if (accessControlMap != null) { (accessControlMap as Map)['enable'] = isAccessControl; if (configMap['vpnProps'] != null) { final vpnPropsRaw = configMap['vpnProps'] as Map; vpnPropsRaw['accessControl'] = accessControlMap; } } if (configMap['vpnProps'] != null) { final vpnPropsRaw = configMap['vpnProps'] as Map; vpnPropsRaw['accessControlProps'] = vpnPropsRaw['accessControl']; } configMap['davProps'] = configMap['dav']; final appSettingProps = configMap['appSetting'] as Map? ?? {}; appSettingProps['restoreStrategy'] = appSettingProps['recoveryStrategy']; configMap['appSettingProps'] = appSettingProps; configMap['proxiesStyleProps'] = configMap['proxiesStyle']; configMap['proxiesStyleProps'] = configMap['proxiesStyle']; // final overwriteMap = configMap['overwrite'] as Map? ?? {}; // configMap['overwriteType'] = overwriteMap['type']; // configMap['scriptId'] = overwriteMap['scriptOverwrite']; List rawScripts = configMap['scripts'] as List? ?? []; if (rawScripts.isEmpty) { final scriptPropsJson = configMap['scriptProps'] as Map?; if (scriptPropsJson != null) { rawScripts = scriptPropsJson['scripts'] as List? ?? []; } } final Map idMap = {}; final List