2025-07-31 17:09:18 +08:00
|
|
|
import 'dart:async';
|
|
|
|
|
import 'dart:convert';
|
|
|
|
|
import 'dart:io';
|
|
|
|
|
|
|
|
|
|
import 'package:fl_clash/common/common.dart';
|
|
|
|
|
import 'package:fl_clash/core/core.dart';
|
|
|
|
|
import 'package:fl_clash/core/interface.dart';
|
|
|
|
|
import 'package:fl_clash/enum/enum.dart';
|
|
|
|
|
import 'package:fl_clash/models/models.dart';
|
|
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
|
import 'package:path/path.dart';
|
|
|
|
|
|
|
|
|
|
class CoreController {
|
|
|
|
|
static CoreController? _instance;
|
|
|
|
|
late CoreHandlerInterface _interface;
|
|
|
|
|
|
|
|
|
|
CoreController._internal() {
|
|
|
|
|
if (system.isAndroid) {
|
|
|
|
|
_interface = coreLib!;
|
|
|
|
|
} else {
|
|
|
|
|
_interface = coreService!;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
factory CoreController() {
|
|
|
|
|
_instance ??= CoreController._internal();
|
|
|
|
|
return _instance!;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-27 23:39:20 +08:00
|
|
|
bool get isCompleted => _interface.completer.isCompleted;
|
|
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
Future<String> preload() {
|
|
|
|
|
return _interface.preload();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Future<void> initGeo() async {
|
|
|
|
|
final homePath = await appPath.homeDirPath;
|
|
|
|
|
final homeDir = Directory(homePath);
|
|
|
|
|
final isExists = await homeDir.exists();
|
|
|
|
|
if (!isExists) {
|
|
|
|
|
await homeDir.create(recursive: true);
|
|
|
|
|
}
|
|
|
|
|
const geoFileNameList = [MMDB, GEOIP, GEOSITE, ASN];
|
|
|
|
|
try {
|
|
|
|
|
for (final geoFileName in geoFileNameList) {
|
|
|
|
|
final geoFile = File(join(homePath, geoFileName));
|
|
|
|
|
final isExists = await geoFile.exists();
|
|
|
|
|
if (isExists) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
final data = await rootBundle.load('assets/data/$geoFileName');
|
|
|
|
|
List<int> bytes = data.buffer.asUint8List();
|
|
|
|
|
await geoFile.writeAsBytes(bytes, flush: true);
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
exit(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<bool> init(int version) async {
|
|
|
|
|
await initGeo();
|
|
|
|
|
final homeDirPath = await appPath.homeDirPath;
|
|
|
|
|
return await _interface.init(
|
|
|
|
|
InitParams(homeDir: homeDirPath, version: version),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 11:23:09 +08:00
|
|
|
Future<void> shutdown(bool isUser) async {
|
|
|
|
|
await _interface.shutdown(isUser);
|
2025-07-31 17:09:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FutureOr<bool> get isInit => _interface.isInit;
|
|
|
|
|
|
2025-12-16 11:23:09 +08:00
|
|
|
Future<String> validateConfig(String path) async {
|
2025-09-23 21:02:47 +08:00
|
|
|
final res = await _interface.validateConfig(path);
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 11:23:09 +08:00
|
|
|
Future<String> validateConfigWithData(String data) async {
|
|
|
|
|
final path = await appPath.tempFilePath;
|
|
|
|
|
final file = File(path);
|
|
|
|
|
await file.safeWriteAsString(data);
|
2025-09-23 21:02:47 +08:00
|
|
|
final res = await _interface.validateConfig(path);
|
2025-12-16 11:23:09 +08:00
|
|
|
await File(path).safeDelete();
|
2025-09-23 21:02:47 +08:00
|
|
|
return res;
|
2025-07-31 17:09:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<String> updateConfig(UpdateParams updateParams) async {
|
|
|
|
|
return await _interface.updateConfig(updateParams);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-14 15:13:52 +08:00
|
|
|
Future<String> setupConfig({
|
|
|
|
|
required SetupParams params,
|
|
|
|
|
required SetupState setupState,
|
2025-09-27 23:39:20 +08:00
|
|
|
VoidCallback? preloadInvoke,
|
|
|
|
|
}) async {
|
|
|
|
|
final res = _interface.setupConfig(params);
|
|
|
|
|
if (preloadInvoke != null) {
|
|
|
|
|
preloadInvoke();
|
|
|
|
|
}
|
|
|
|
|
return res;
|
2025-07-31 17:09:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<List<Group>> getProxiesGroups({
|
|
|
|
|
required ProxiesSortType sortType,
|
|
|
|
|
required DelayMap delayMap,
|
2025-10-14 15:13:52 +08:00
|
|
|
required Map<String, String> selectedMap,
|
2025-07-31 17:09:18 +08:00
|
|
|
required String defaultTestUrl,
|
|
|
|
|
}) async {
|
2025-12-16 11:23:09 +08:00
|
|
|
final proxiesData = await _interface.getProxies();
|
|
|
|
|
return toGroupsTask(
|
|
|
|
|
ComputeGroupsState(
|
|
|
|
|
proxiesData: proxiesData,
|
2025-07-31 17:09:18 +08:00
|
|
|
sortType: sortType,
|
|
|
|
|
delayMap: delayMap,
|
|
|
|
|
selectedMap: selectedMap,
|
|
|
|
|
defaultTestUrl: defaultTestUrl,
|
2025-12-16 11:23:09 +08:00
|
|
|
),
|
|
|
|
|
);
|
2025-07-31 17:09:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams) async {
|
|
|
|
|
return await _interface.changeProxy(changeProxyParams);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<List<TrackerInfo>> getConnections() async {
|
|
|
|
|
final res = await _interface.getConnections();
|
|
|
|
|
final connectionsData = json.decode(res) as Map;
|
|
|
|
|
final connectionsRaw = connectionsData['connections'] as List? ?? [];
|
|
|
|
|
return connectionsRaw.map((e) => TrackerInfo.fromJson(e)).toList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void closeConnection(String id) {
|
|
|
|
|
_interface.closeConnection(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void closeConnections() {
|
|
|
|
|
_interface.closeConnections();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void resetConnections() {
|
|
|
|
|
_interface.resetConnections();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<List<ExternalProvider>> getExternalProviders() async {
|
|
|
|
|
final externalProvidersRawString = await _interface.getExternalProviders();
|
|
|
|
|
if (externalProvidersRawString.isEmpty) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
2025-12-16 11:23:09 +08:00
|
|
|
final externalProviders =
|
|
|
|
|
(await externalProvidersRawString.commonToJSON<List<dynamic>>())
|
|
|
|
|
.map((item) => ExternalProvider.fromJson(item))
|
|
|
|
|
.toList();
|
|
|
|
|
return externalProviders;
|
2025-07-31 17:09:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<ExternalProvider?> getExternalProvider(
|
|
|
|
|
String externalProviderName,
|
|
|
|
|
) async {
|
|
|
|
|
final externalProvidersRawString = await _interface.getExternalProvider(
|
|
|
|
|
externalProviderName,
|
|
|
|
|
);
|
|
|
|
|
if (externalProvidersRawString.isEmpty) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return ExternalProvider.fromJson(json.decode(externalProvidersRawString));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<String> updateGeoData(UpdateGeoDataParams params) {
|
|
|
|
|
return _interface.updateGeoData(params);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<String> sideLoadExternalProvider({
|
|
|
|
|
required String providerName,
|
|
|
|
|
required String data,
|
|
|
|
|
}) {
|
|
|
|
|
return _interface.sideLoadExternalProvider(
|
|
|
|
|
providerName: providerName,
|
|
|
|
|
data: data,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<String> updateExternalProvider({required String providerName}) async {
|
|
|
|
|
return _interface.updateExternalProvider(providerName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<bool> startListener() async {
|
|
|
|
|
return await _interface.startListener();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<bool> stopListener() async {
|
|
|
|
|
return await _interface.stopListener();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<Delay> getDelay(String url, String proxyName) async {
|
|
|
|
|
final data = await _interface.asyncTestDelay(url, proxyName);
|
|
|
|
|
return Delay.fromJson(json.decode(data));
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 11:23:09 +08:00
|
|
|
Future<Map<String, dynamic>> getConfig(int id) async {
|
|
|
|
|
final profilePath = await appPath.getProfilePath(id.toString());
|
2025-07-31 17:09:18 +08:00
|
|
|
final res = await _interface.getConfig(profilePath);
|
|
|
|
|
if (res.isSuccess) {
|
2025-12-16 11:23:09 +08:00
|
|
|
final data = Map<String, dynamic>.from(res.data);
|
|
|
|
|
data['rules'] = data['rule'];
|
|
|
|
|
data.remove('rule');
|
|
|
|
|
return data;
|
2025-07-31 17:09:18 +08:00
|
|
|
} else {
|
|
|
|
|
throw res.message;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<Traffic> getTraffic(bool onlyStatisticsProxy) async {
|
|
|
|
|
final trafficString = await _interface.getTraffic(onlyStatisticsProxy);
|
|
|
|
|
if (trafficString.isEmpty) {
|
|
|
|
|
return Traffic();
|
|
|
|
|
}
|
|
|
|
|
return Traffic.fromJson(json.decode(trafficString));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<IpInfo?> getCountryCode(String ip) async {
|
|
|
|
|
final countryCode = await _interface.getCountryCode(ip);
|
|
|
|
|
if (countryCode.isEmpty) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return IpInfo(ip: ip, countryCode: countryCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<Traffic> getTotalTraffic(bool onlyStatisticsProxy) async {
|
|
|
|
|
final totalTrafficString = await _interface.getTotalTraffic(
|
|
|
|
|
onlyStatisticsProxy,
|
|
|
|
|
);
|
|
|
|
|
if (totalTrafficString.isEmpty) {
|
|
|
|
|
return Traffic();
|
|
|
|
|
}
|
|
|
|
|
return Traffic.fromJson(json.decode(totalTrafficString));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<int> getMemory() async {
|
|
|
|
|
final value = await _interface.getMemory();
|
|
|
|
|
if (value.isEmpty) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return int.parse(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void resetTraffic() {
|
|
|
|
|
_interface.resetTraffic();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void startLog() {
|
|
|
|
|
_interface.startLog();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void stopLog() {
|
|
|
|
|
_interface.stopLog();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> requestGc() async {
|
|
|
|
|
await _interface.forceGc();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> destroy() async {
|
|
|
|
|
await _interface.destroy();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> crash() async {
|
|
|
|
|
await _interface.crash();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<String> deleteFile(String path) async {
|
|
|
|
|
return await _interface.deleteFile(path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final coreController = CoreController();
|