Files
MWClash/lib/models/profile.dart
chen08209 d89481114f Fix windows tray issues
Support setting bypassDomain

Update flutter version

Fix android service issues

Fix macos dock exit button issues

Add route address setting

Optimize provider view
2024-11-17 20:42:01 +08:00

139 lines
3.9 KiB
Dart

// ignore_for_file: invalid_annotation_target
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:fl_clash/clash/core.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'generated/profile.freezed.dart';
part 'generated/profile.g.dart';
typedef SelectedMap = Map<String, String>;
@freezed
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
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,
@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,
);
}
}
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<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 ?? other.getFileNameForDisposition(disposition) ?? id,
subscriptionInfo: SubscriptionInfo.formHString(userinfo),
).saveFile(response.data);
}
Future<Profile> saveFile(Uint8List bytes) async {
final message = await clashCore.validateConfig(utf8.decode(bytes));
if (message.isNotEmpty) {
throw message;
}
final path = await appPath.getProfilePath(id);
final file = File(path!);
final isExists = await file.exists();
if (!isExists) {
await file.create(recursive: true);
}
await file.writeAsBytes(bytes);
return copyWith(lastUpdateDate: DateTime.now());
}
Future<Profile> saveFileWithString(String value) async {
final message = await clashCore.validateConfig(value);
if (message.isNotEmpty) {
throw message;
}
final path = await appPath.getProfilePath(id);
final file = File(path!);
final isExists = await file.exists();
if (!isExists) {
await file.create(recursive: true);
}
await file.writeAsString(value);
return copyWith(lastUpdateDate: DateTime.now());
}
}