Files
MWClash/lib/models/profile.dart
chen08209 ddc07e9f29 Add android separates the core process
Support core status check and force restart

Optimize proxies page and access page

Update flutter and pub dependencies

Optimize more details
2025-09-17 17:12:11 +08:00

196 lines
5.5 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/core/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'clash_config.dart';
part 'generated/profile.freezed.dart';
part 'generated/profile.g.dart';
typedef SelectedMap = Map<String, String>;
@freezed
abstract class SubscriptionInfo with _$SubscriptionInfo {
const factory SubscriptionInfo({
@Default(0) int upload,
@Default(0) int download,
@Default(0) int total,
@Default(0) int expire,
}) = _SubscriptionInfo;
factory SubscriptionInfo.fromJson(Map<String, Object?> json) =>
_$SubscriptionInfoFromJson(json);
factory SubscriptionInfo.formHString(String? info) {
if (info == null) return const SubscriptionInfo();
final list = info.split(';');
Map<String, int?> map = {};
for (final i in list) {
final keyValue = i.trim().split('=');
map[keyValue[0]] = int.tryParse(keyValue[1]);
}
return SubscriptionInfo(
upload: map['upload'] ?? 0,
download: map['download'] ?? 0,
total: map['total'] ?? 0,
expire: map['expire'] ?? 0,
);
}
}
@freezed
abstract class Profile with _$Profile {
const factory Profile({
required String id,
String? label,
String? currentGroupName,
@Default('') String url,
DateTime? lastUpdateDate,
required Duration autoUpdateDuration,
SubscriptionInfo? subscriptionInfo,
@Default(true) bool autoUpdate,
@Default({}) SelectedMap selectedMap,
@Default({}) Set<String> unfoldSet,
@Default(OverrideData()) OverrideData overrideData,
@JsonKey(includeToJson: false, includeFromJson: false)
@Default(false)
bool isUpdating,
}) = _Profile;
factory Profile.fromJson(Map<String, Object?> json) =>
_$ProfileFromJson(json);
factory Profile.normal({String? label, String url = ''}) {
return Profile(
label: label,
url: url,
id: DateTime.now().millisecondsSinceEpoch.toString(),
autoUpdateDuration: defaultUpdateDuration,
);
}
}
@freezed
abstract class OverrideData with _$OverrideData {
const factory OverrideData({
@Default(false) bool enable,
@Default(OverrideRule()) OverrideRule rule,
}) = _OverrideData;
factory OverrideData.fromJson(Map<String, Object?> json) =>
_$OverrideDataFromJson(json);
}
extension OverrideDataExt on OverrideData {
List<String> get runningRule {
if (!enable) {
return [];
}
return rule.rules.map((item) => item.value).toList();
}
}
@freezed
abstract class OverrideRule with _$OverrideRule {
const factory OverrideRule({
@Default(OverrideRuleType.added) OverrideRuleType type,
@Default([]) List<Rule> overrideRules,
@Default([]) List<Rule> addedRules,
}) = _OverrideRule;
factory OverrideRule.fromJson(Map<String, Object?> json) =>
_$OverrideRuleFromJson(json);
}
extension OverrideRuleExt on OverrideRule {
List<Rule> get rules => switch (type == OverrideRuleType.override) {
true => overrideRules,
false => addedRules,
};
OverrideRule updateRules(List<Rule> Function(List<Rule> rules) builder) {
if (type == OverrideRuleType.added) {
return copyWith(addedRules: builder(addedRules));
}
return copyWith(overrideRules: builder(overrideRules));
}
}
extension ProfilesExt on List<Profile> {
Profile? getProfile(String? profileId) {
final index = indexWhere((profile) => profile.id == profileId);
return index == -1 ? null : this[index];
}
}
extension ProfileExtension on Profile {
ProfileType get type =>
url.isEmpty == true ? ProfileType.file : ProfileType.url;
bool get realAutoUpdate => url.isEmpty == true ? false : autoUpdate;
Future<void> checkAndUpdate() async {
final isExists = await check();
if (!isExists) {
if (url.isNotEmpty) {
await update();
}
}
}
Future<bool> check() async {
final profilePath = await appPath.getProfilePath(id);
return await File(profilePath).exists();
}
Future<File> getFile() async {
final path = await appPath.getProfilePath(id);
final file = File(path);
final isExists = await file.exists();
if (!isExists) {
await file.create(recursive: true);
}
return file;
}
Future<int> get profileLastModified async {
final file = await getFile();
return (await file.lastModified()).microsecondsSinceEpoch;
}
Future<Profile> update() async {
final response = await request.getFileResponseForUrl(url);
final disposition = response.headers.value('content-disposition');
final userinfo = response.headers.value('subscription-userinfo');
return await copyWith(
label: label ?? utils.getFileNameForDisposition(disposition) ?? id,
subscriptionInfo: SubscriptionInfo.formHString(userinfo),
).saveFile(response.data);
}
Future<Profile> saveFile(Uint8List bytes) async {
final message = await coreController.validateConfig(utf8.decode(bytes));
if (message.isNotEmpty) {
throw message;
}
final file = await getFile();
await file.writeAsBytes(bytes);
return copyWith(lastUpdateDate: DateTime.now());
}
Future<Profile> saveFileWithString(String value) async {
final message = await coreController.validateConfig(value);
if (message.isNotEmpty) {
throw message;
}
final file = await getFile();
await file.writeAsString(value);
return copyWith(lastUpdateDate: DateTime.now());
}
}