new: add custom url-test for each repository

This commit is contained in:
hiddify-com
2024-07-29 13:11:51 +02:00
parent ccbc416c49
commit b9afe586bf
14 changed files with 417 additions and 157 deletions

View File

@@ -22,12 +22,14 @@ abstract interface class ConnectionRepository {
String fileName,
String profileName,
bool disableMemoryLimit,
String? testUrl,
);
TaskEither<ConnectionFailure, Unit> disconnect();
TaskEither<ConnectionFailure, Unit> reconnect(
String fileName,
String profileName,
bool disableMemoryLimit,
String? testUrl,
);
}
@@ -101,11 +103,16 @@ class ConnectionRepositoryImpl with ExceptionHandler, InfraLogger implements Con
@visibleForTesting
TaskEither<ConnectionFailure, Unit> applyConfigOption(
SingboxConfigOption options,
String? testUrl,
) {
return exceptionHandler(
() {
_configOptionsSnapshot = options;
return singbox.changeOptions(options).mapLeft(InvalidConfigOption.new).run();
var newOptions = options;
if (testUrl != null) {
newOptions = options.copyWith(connectionTestUrl: testUrl);
}
return singbox.changeOptions(newOptions).mapLeft(InvalidConfigOption.new).run();
},
UnexpectedConnectionFailure.new,
);
@@ -138,10 +145,11 @@ class ConnectionRepositoryImpl with ExceptionHandler, InfraLogger implements Con
String fileName,
String profileName,
bool disableMemoryLimit,
String? testUrl,
) {
return TaskEither<ConnectionFailure, Unit>.Do(
($) async {
final options = await $(getConfigOption());
var options = await $(getConfigOption());
loggy.info(
"config options: ${options.format()}\nMemory Limit: ${!disableMemoryLimit}",
);
@@ -159,7 +167,7 @@ class ConnectionRepositoryImpl with ExceptionHandler, InfraLogger implements Con
}),
);
await $(setup());
await $(applyConfigOption(options));
await $(applyConfigOption(options, testUrl));
return await $(
singbox
.start(
@@ -203,23 +211,26 @@ class ConnectionRepositoryImpl with ExceptionHandler, InfraLogger implements Con
String fileName,
String profileName,
bool disableMemoryLimit,
String? testUrl,
) {
return exceptionHandler(
() async {
return getConfigOption()
.flatMap((options) => applyConfigOption(options))
.andThen(
() => singbox
.restart(
profilePathResolver.file(fileName).path,
profileName,
disableMemoryLimit,
)
.mapLeft(UnexpectedConnectionFailure.new),
)
.run();
return TaskEither<ConnectionFailure, Unit>.Do(
($) async {
var options = await $(getConfigOption());
loggy.info(
"config options: ${options.format()}\nMemory Limit: ${!disableMemoryLimit}",
);
await $(applyConfigOption(options, testUrl));
return await $(
singbox
.restart(
profilePathResolver.file(fileName).path,
profileName,
disableMemoryLimit,
)
.mapLeft(UnexpectedConnectionFailure.new),
);
},
UnexpectedConnectionFailure.new,
);
).handleExceptions(UnexpectedConnectionFailure.new);
}
}

View File

@@ -103,6 +103,7 @@ class ConnectionNotifier extends _$ConnectionNotifier with AppLogger {
profile.id,
profile.name,
ref.read(Preferences.disableMemoryLimit),
profile.testUrl,
)
.mapLeft((err) {
loggy.warning("error reconnecting", err);
@@ -133,6 +134,7 @@ class ConnectionNotifier extends _$ConnectionNotifier with AppLogger {
activeProfile.id,
activeProfile.name,
ref.read(Preferences.disableMemoryLimit),
activeProfile.testUrl,
)
.mapLeft((err) async {
loggy.warning("error connecting", err);

View File

@@ -5,8 +5,7 @@ import 'package:hiddify/features/profile/model/profile_entity.dart';
extension ProfileEntityMapper on ProfileEntity {
ProfileEntriesCompanion toEntry() {
return switch (this) {
RemoteProfileEntity(:final url, :final options, :final subInfo) =>
ProfileEntriesCompanion.insert(
RemoteProfileEntity(:final url, :final options, :final subInfo) => ProfileEntriesCompanion.insert(
id: id,
type: ProfileType.remote,
active: active,
@@ -20,6 +19,7 @@ extension ProfileEntityMapper on ProfileEntity {
expire: Value(subInfo?.expire),
webPageUrl: Value(subInfo?.webPageUrl),
supportUrl: Value(subInfo?.supportUrl),
testUrl: Value(testUrl),
),
LocalProfileEntity() => ProfileEntriesCompanion.insert(
id: id,
@@ -41,6 +41,7 @@ extension RemoteProfileEntityMapper on RemoteProfileEntity {
expire: Value(subInfo?.expire),
webPageUrl: Value(subInfo?.webPageUrl),
supportUrl: Value(subInfo?.supportUrl),
testUrl: Value(testUrl),
);
}
}
@@ -73,12 +74,14 @@ extension ProfileEntryMapper on ProfileEntry {
lastUpdate: lastUpdate,
options: options,
subInfo: subInfo,
testUrl: testUrl,
),
ProfileType.local => LocalProfileEntity(
id: id,
active: active,
name: name,
lastUpdate: lastUpdate,
testUrl: testUrl,
),
};
}

View File

@@ -24,14 +24,12 @@ abstract class ProfileParser {
var name = '';
if (headers['profile-title'] case [final titleHeader]) {
if (titleHeader.startsWith("base64:")) {
name =
utf8.decode(base64.decode(titleHeader.replaceFirst("base64:", "")));
name = utf8.decode(base64.decode(titleHeader.replaceFirst("base64:", "")));
} else {
name = titleHeader.trim();
}
}
if (headers['content-disposition'] case [final contentDispositionHeader]
when name.isEmpty) {
if (headers['content-disposition'] case [final contentDispositionHeader] when name.isEmpty) {
final regExp = RegExp('filename="([^"]*)"');
final match = regExp.firstMatch(contentDispositionHeader);
if (match != null && match.groupCount >= 1) {
@@ -52,19 +50,20 @@ abstract class ProfileParser {
final updateInterval = Duration(hours: int.parse(updateIntervalStr));
options = ProfileOptions(updateInterval: updateInterval);
}
String? testUrl;
if (headers['test-url'] case [final testUrl_] when isUrl(testUrl_)) {
testUrl = testUrl_;
}
SubscriptionInfo? subInfo;
if (headers['subscription-userinfo'] case [final subInfoStr]) {
subInfo = parseSubscriptionInfo(subInfoStr);
}
if (subInfo != null) {
if (headers['profile-web-page-url'] case [final profileWebPageUrl]
when isUrl(profileWebPageUrl)) {
if (headers['profile-web-page-url'] case [final profileWebPageUrl] when isUrl(profileWebPageUrl)) {
subInfo = subInfo.copyWith(webPageUrl: profileWebPageUrl);
}
if (headers['support-url'] case [final profileSupportUrl]
when isUrl(profileSupportUrl)) {
if (headers['support-url'] case [final profileSupportUrl] when isUrl(profileSupportUrl)) {
subInfo = subInfo.copyWith(supportUrl: profileSupportUrl);
}
}
@@ -77,23 +76,16 @@ abstract class ProfileParser {
lastUpdate: DateTime.now(),
options: options,
subInfo: subInfo,
testUrl: testUrl,
);
}
static SubscriptionInfo? parseSubscriptionInfo(String subInfoStr) {
final values = subInfoStr.split(';');
final map = {
for (final v in values)
v.split('=').first.trim():
num.tryParse(v.split('=').second.trim())?.toInt(),
for (final v in values) v.split('=').first.trim(): num.tryParse(v.split('=').second.trim())?.toInt(),
};
if (map
case {
"upload": final upload?,
"download": final download?,
"total": var total,
"expire": var expire
}) {
if (map case {"upload": final upload?, "download": final download?, "total": var total, "expire": var expire}) {
total = (total == null || total == 0) ? infiniteTrafficThreshold : total;
expire = (expire == null || expire == 0) ? infiniteTimeThreshold : expire;
return SubscriptionInfo(

View File

@@ -282,6 +282,7 @@ class ProfileRepositoryImpl with ExceptionHandler, InfraLogger implements Profil
? profilePatch.copyWith(
name: Value(baseProfile.name),
url: Value(baseProfile.url),
testUrl: Value(baseProfile.testUrl),
updateInterval: Value(baseProfile.options?.updateInterval),
)
: profilePatch,
@@ -349,6 +350,7 @@ class ProfileRepositoryImpl with ExceptionHandler, InfraLogger implements Profil
'profile-update-interval',
'support-url',
'profile-web-page-url',
'test-url',
];
@visibleForTesting

View File

@@ -15,6 +15,7 @@ sealed class ProfileEntity with _$ProfileEntity {
required String name,
required String url,
required DateTime lastUpdate,
String? testUrl,
ProfileOptions? options,
SubscriptionInfo? subInfo,
}) = RemoteProfileEntity;
@@ -24,6 +25,7 @@ sealed class ProfileEntity with _$ProfileEntity {
required bool active,
required String name,
required DateTime lastUpdate,
String? testUrl,
}) = LocalProfileEntity;
}