fix(core): send changed geo resource URLs to the Core without a full apply

The Core only learned geox-url from a full config apply, so after a link
was edited on the resources page both a manual sync and the auto updater
kept downloading from the previous link until the profile was applied
again. The URLs now travel with the update params, which are pushed
whenever the patch config changes. Only a link that changed is written,
because mihomo's updaters read these links without a lock.

Changelog: A changed resource link is now used by the next sync and by auto update
This commit is contained in:
chen08209
2026-10-02 12:08:25 +08:00
parent e55f6d1004
commit ff23f30c31
9 changed files with 138 additions and 31 deletions
+3
View File
@@ -318,6 +318,9 @@ func updateConfig(params *UpdateParams) error {
}
updateListeners(currentConfig)
for geoType, link := range params.GeoXUrl {
setGeoResourceUrl(geoType, link)
}
syncGeoUpdater(params.GeoAutoUpdate, params.GeoUpdateInterval)
if routeChanged {
bumpRouteEpoch()
+56 -4
View File
@@ -122,6 +122,55 @@ func TestUpdateConfigAppliesAllowLan(t *testing.T) {
}
}
func TestUpdateConfigAppliesGeoResourceUrls(t *testing.T) {
withCurrentConfig(t, &config.Config{General: &config.General{}, Controller: &config.Controller{}})
requested := map[string]string{
"mmdb": "https://example.test/geoip.metadb",
"asn": "https://example.test/GeoLite2-ASN.mmdb",
"geoip": "https://example.test/geoip.dat",
"geosite": "https://example.test/geosite.dat",
}
for _, resource := range geoResources {
resource, previous := resource, resource.url()
t.Cleanup(func() { resource.setUrl(previous) })
}
if err := updateConfig(&UpdateParams{GeoXUrl: requested}); err != nil {
t.Fatalf("updateConfig error: %v", err)
}
for key, want := range requested {
if got := geoResources[strings.ToUpper(key)].url(); got != want {
t.Errorf("%s url = %q, want %q; sync and the auto updater would keep the old link", key, got, want)
}
}
}
func TestSetGeoResourceUrlWritesOnlyAChangedLink(t *testing.T) {
const current = "https://example.test/geosite.dat"
var writes []string
previous := geoResources
geoResources = map[string]geoResource{
"GEOSITE": {
url: func() string { return current },
setUrl: func(link string) { writes = append(writes, link) },
},
}
t.Cleanup(func() { geoResources = previous })
setGeoResourceUrl("geosite", current)
setGeoResourceUrl("geosite", "")
setGeoResourceUrl("nope", "https://example.test/nope.dat")
if len(writes) != 0 {
t.Fatalf("writes = %q, want none for an unchanged, empty or unknown link", writes)
}
setGeoResourceUrl("geosite", "https://example.test/next.dat")
if len(writes) != 1 || writes[0] != "https://example.test/next.dat" {
t.Errorf("writes = %q, want the changed link once", writes)
}
}
func TestUpdateConfigAppliesAuthenticationAndClearsLoopbackExemptions(t *testing.T) {
withCurrentConfig(t, &config.Config{General: &config.General{}, Controller: &config.Controller{}})
currentConfig.General.SkipAuthPrefixes = []netip.Prefix{
@@ -359,7 +408,7 @@ func TestHandleUpdateGeoDataRejectsAnUnknownResource(t *testing.T) {
if message := handleUpdateGeoData("NOPE"); message == "" {
t.Error("an unknown geo resource reported success and silently did nothing")
}
if _, exists := geoResourceUpdaters["MMDB"]; !exists {
if _, exists := geoResources["MMDB"]; !exists {
t.Error("MMDB is missing from the geo resource table")
}
}
@@ -415,10 +464,13 @@ func settleMessageBatcher() {
func withGeoResourceUpdaters(t *testing.T, updaters map[string]func() error) {
t.Helper()
previous := geoResourceUpdaters
geoResourceUpdaters = updaters
previous := geoResources
geoResources = make(map[string]geoResource, len(updaters))
for name, update := range updaters {
geoResources[name] = geoResource{update: update}
}
t.Cleanup(func() {
geoResourceUpdaters = previous
geoResources = previous
for name := range updaters {
releaseGeoUpdate(name)
}
+1
View File
@@ -36,6 +36,7 @@ type UpdateParams struct {
Authentication *[]string `json:"authentication"`
GeoAutoUpdate *bool `json:"geo-auto-update"`
GeoUpdateInterval *int `json:"geo-update-interval"`
GeoXUrl map[string]string `json:"geox-url"`
}
type tunSchema struct {
+26 -7
View File
@@ -26,6 +26,7 @@ import (
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/common/observable"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/geodata"
"github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/component/updater"
"github.com/metacubex/mihomo/config"
@@ -428,11 +429,29 @@ func handleGetExternalProvider(externalProviderName string) *ExternalProvider {
return externalProvider
}
var geoResourceUpdaters = map[string]func() error{
"MMDB": updater.UpdateMMDB,
"ASN": updater.UpdateASN,
"GEOIP": updater.UpdateGeoIp,
"GEOSITE": updater.UpdateGeoSite,
type geoResource struct {
update func() error
url func() string
setUrl func(string)
}
var geoResources = map[string]geoResource{
"MMDB": {update: updater.UpdateMMDB, url: geodata.MmdbUrl, setUrl: geodata.SetMmdbUrl},
"ASN": {update: updater.UpdateASN, url: geodata.ASNUrl, setUrl: geodata.SetASNUrl},
"GEOIP": {update: updater.UpdateGeoIp, url: geodata.GeoIpUrl, setUrl: geodata.SetGeoIpUrl},
"GEOSITE": {update: updater.UpdateGeoSite, url: geodata.GeoSiteUrl, setUrl: geodata.SetGeoSiteUrl},
}
// mihomo's updaters read these links without a lock, so an unchanged one is never rewritten.
func setGeoResourceUrl(geoType string, link string) {
resource, exist := geoResources[strings.ToUpper(geoType)]
if !exist {
log.Warnln("geox-url: unknown geo resource %q", geoType)
return
}
if link != "" && link != resource.url() {
resource.setUrl(link)
}
}
const (
@@ -494,7 +513,7 @@ func releaseGeoUpdateFromHook(geoType string) {
}
func handleUpdateGeoData(geoType string) string {
update, exist := geoResourceUpdaters[geoType]
resource, exist := geoResources[geoType]
if !exist {
logError("updateGeoData: unknown geo resource %q", geoType)
return "unknown geo resource: " + geoType
@@ -504,7 +523,7 @@ func handleUpdateGeoData(geoType string) string {
}
safeGoDetached("updateGeoData("+geoType+")", func() {
defer releaseGeoUpdate(geoType)
if err := update(); err != nil {
if err := resource.update(); err != nil {
logError("updateGeoData(%s) error: %v", geoType, err)
}
})
+1
View File
@@ -1149,6 +1149,7 @@ extension PatchClashConfigExt on PatchClashConfig {
mixedPort: mixedPort,
geoAutoUpdate: geoAutoUpdate,
geoUpdateInterval: geoUpdateInterval,
geoXUrl: geoXUrl.raw,
);
}
}
+1
View File
@@ -34,6 +34,7 @@ abstract class UpdateParams with _$UpdateParams {
@Default([]) List<String> authentication,
@Default(false) @JsonKey(name: 'geo-auto-update') bool geoAutoUpdate,
@Default(24) @JsonKey(name: 'geo-update-interval') int geoUpdateInterval,
@Default({}) @JsonKey(name: 'geox-url') Map<String, String> geoXUrl,
}) = _UpdateParams;
factory UpdateParams.fromJson(Map<String, dynamic> json) =>
+29 -20
View File
@@ -295,7 +295,7 @@ as String,
/// @nodoc
mixin _$UpdateParams {
Tun get tun;@JsonKey(name: 'mixed-port') int get mixedPort;@JsonKey(name: 'allow-lan') bool get allowLan;@JsonKey(name: 'find-process-mode') FindProcessMode get findProcessMode; Mode get mode;@JsonKey(name: 'log-level') LogLevel get logLevel; bool get ipv6;@JsonKey(name: 'tcp-concurrent') bool get tcpConcurrent;@JsonKey(name: 'external-controller') ExternalControllerStatus get externalController;@JsonKey(name: 'unified-delay') bool get unifiedDelay; List<String> get authentication;@JsonKey(name: 'geo-auto-update') bool get geoAutoUpdate;@JsonKey(name: 'geo-update-interval') int get geoUpdateInterval;
Tun get tun;@JsonKey(name: 'mixed-port') int get mixedPort;@JsonKey(name: 'allow-lan') bool get allowLan;@JsonKey(name: 'find-process-mode') FindProcessMode get findProcessMode; Mode get mode;@JsonKey(name: 'log-level') LogLevel get logLevel; bool get ipv6;@JsonKey(name: 'tcp-concurrent') bool get tcpConcurrent;@JsonKey(name: 'external-controller') ExternalControllerStatus get externalController;@JsonKey(name: 'unified-delay') bool get unifiedDelay; List<String> get authentication;@JsonKey(name: 'geo-auto-update') bool get geoAutoUpdate;@JsonKey(name: 'geo-update-interval') int get geoUpdateInterval;@JsonKey(name: 'geox-url') Map<String, String> get geoXUrl;
/// Create a copy of UpdateParams
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -309,20 +309,20 @@ $UpdateParamsCopyWith<UpdateParams> get copyWith => _$UpdateParamsCopyWithImpl<U
@override
bool operator ==(Object other) {
final _this = this as UpdateParams;
return identical(this, other) || (other.runtimeType == runtimeType&&other is UpdateParams&&(identical(other.tun, _this.tun) || other.tun == _this.tun)&&(identical(other.mixedPort, _this.mixedPort) || other.mixedPort == _this.mixedPort)&&(identical(other.allowLan, _this.allowLan) || other.allowLan == _this.allowLan)&&(identical(other.findProcessMode, _this.findProcessMode) || other.findProcessMode == _this.findProcessMode)&&(identical(other.mode, _this.mode) || other.mode == _this.mode)&&(identical(other.logLevel, _this.logLevel) || other.logLevel == _this.logLevel)&&(identical(other.ipv6, _this.ipv6) || other.ipv6 == _this.ipv6)&&(identical(other.tcpConcurrent, _this.tcpConcurrent) || other.tcpConcurrent == _this.tcpConcurrent)&&(identical(other.externalController, _this.externalController) || other.externalController == _this.externalController)&&(identical(other.unifiedDelay, _this.unifiedDelay) || other.unifiedDelay == _this.unifiedDelay)&&const DeepCollectionEquality().equals(other.authentication, _this.authentication)&&(identical(other.geoAutoUpdate, _this.geoAutoUpdate) || other.geoAutoUpdate == _this.geoAutoUpdate)&&(identical(other.geoUpdateInterval, _this.geoUpdateInterval) || other.geoUpdateInterval == _this.geoUpdateInterval));
return identical(this, other) || (other.runtimeType == runtimeType&&other is UpdateParams&&(identical(other.tun, _this.tun) || other.tun == _this.tun)&&(identical(other.mixedPort, _this.mixedPort) || other.mixedPort == _this.mixedPort)&&(identical(other.allowLan, _this.allowLan) || other.allowLan == _this.allowLan)&&(identical(other.findProcessMode, _this.findProcessMode) || other.findProcessMode == _this.findProcessMode)&&(identical(other.mode, _this.mode) || other.mode == _this.mode)&&(identical(other.logLevel, _this.logLevel) || other.logLevel == _this.logLevel)&&(identical(other.ipv6, _this.ipv6) || other.ipv6 == _this.ipv6)&&(identical(other.tcpConcurrent, _this.tcpConcurrent) || other.tcpConcurrent == _this.tcpConcurrent)&&(identical(other.externalController, _this.externalController) || other.externalController == _this.externalController)&&(identical(other.unifiedDelay, _this.unifiedDelay) || other.unifiedDelay == _this.unifiedDelay)&&const DeepCollectionEquality().equals(other.authentication, _this.authentication)&&(identical(other.geoAutoUpdate, _this.geoAutoUpdate) || other.geoAutoUpdate == _this.geoAutoUpdate)&&(identical(other.geoUpdateInterval, _this.geoUpdateInterval) || other.geoUpdateInterval == _this.geoUpdateInterval)&&const DeepCollectionEquality().equals(other.geoXUrl, _this.geoXUrl));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode {
final _this = this as UpdateParams;
return Object.hash(runtimeType,_this.tun,_this.mixedPort,_this.allowLan,_this.findProcessMode,_this.mode,_this.logLevel,_this.ipv6,_this.tcpConcurrent,_this.externalController,_this.unifiedDelay,const DeepCollectionEquality().hash(_this.authentication),_this.geoAutoUpdate,_this.geoUpdateInterval);
return Object.hash(runtimeType,_this.tun,_this.mixedPort,_this.allowLan,_this.findProcessMode,_this.mode,_this.logLevel,_this.ipv6,_this.tcpConcurrent,_this.externalController,_this.unifiedDelay,const DeepCollectionEquality().hash(_this.authentication),_this.geoAutoUpdate,_this.geoUpdateInterval,const DeepCollectionEquality().hash(_this.geoXUrl));
}
@override
String toString() {
final _this = this as UpdateParams;
return 'UpdateParams(tun: ${_this.tun}, mixedPort: ${_this.mixedPort}, allowLan: ${_this.allowLan}, findProcessMode: ${_this.findProcessMode}, mode: ${_this.mode}, logLevel: ${_this.logLevel}, ipv6: ${_this.ipv6}, tcpConcurrent: ${_this.tcpConcurrent}, externalController: ${_this.externalController}, unifiedDelay: ${_this.unifiedDelay}, authentication: ${_this.authentication}, geoAutoUpdate: ${_this.geoAutoUpdate}, geoUpdateInterval: ${_this.geoUpdateInterval})';
return 'UpdateParams(tun: ${_this.tun}, mixedPort: ${_this.mixedPort}, allowLan: ${_this.allowLan}, findProcessMode: ${_this.findProcessMode}, mode: ${_this.mode}, logLevel: ${_this.logLevel}, ipv6: ${_this.ipv6}, tcpConcurrent: ${_this.tcpConcurrent}, externalController: ${_this.externalController}, unifiedDelay: ${_this.unifiedDelay}, authentication: ${_this.authentication}, geoAutoUpdate: ${_this.geoAutoUpdate}, geoUpdateInterval: ${_this.geoUpdateInterval}, geoXUrl: ${_this.geoXUrl})';
}
@@ -333,7 +333,7 @@ abstract mixin class $UpdateParamsCopyWith<$Res> {
factory $UpdateParamsCopyWith(UpdateParams value, $Res Function(UpdateParams) _then) = _$UpdateParamsCopyWithImpl;
@useResult
$Res call({
Tun tun,@JsonKey(name: 'mixed-port') int mixedPort,@JsonKey(name: 'allow-lan') bool allowLan,@JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode,@JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6,@JsonKey(name: 'tcp-concurrent') bool tcpConcurrent,@JsonKey(name: 'external-controller') ExternalControllerStatus externalController,@JsonKey(name: 'unified-delay') bool unifiedDelay, List<String> authentication,@JsonKey(name: 'geo-auto-update') bool geoAutoUpdate,@JsonKey(name: 'geo-update-interval') int geoUpdateInterval
Tun tun,@JsonKey(name: 'mixed-port') int mixedPort,@JsonKey(name: 'allow-lan') bool allowLan,@JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode,@JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6,@JsonKey(name: 'tcp-concurrent') bool tcpConcurrent,@JsonKey(name: 'external-controller') ExternalControllerStatus externalController,@JsonKey(name: 'unified-delay') bool unifiedDelay, List<String> authentication,@JsonKey(name: 'geo-auto-update') bool geoAutoUpdate,@JsonKey(name: 'geo-update-interval') int geoUpdateInterval,@JsonKey(name: 'geox-url') Map<String, String> geoXUrl
});
@@ -350,7 +350,7 @@ class _$UpdateParamsCopyWithImpl<$Res>
/// Create a copy of UpdateParams
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? tun = null,Object? mixedPort = null,Object? allowLan = null,Object? findProcessMode = null,Object? mode = null,Object? logLevel = null,Object? ipv6 = null,Object? tcpConcurrent = null,Object? externalController = null,Object? unifiedDelay = null,Object? authentication = null,Object? geoAutoUpdate = null,Object? geoUpdateInterval = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? tun = null,Object? mixedPort = null,Object? allowLan = null,Object? findProcessMode = null,Object? mode = null,Object? logLevel = null,Object? ipv6 = null,Object? tcpConcurrent = null,Object? externalController = null,Object? unifiedDelay = null,Object? authentication = null,Object? geoAutoUpdate = null,Object? geoUpdateInterval = null,Object? geoXUrl = null,}) {
return _then(UpdateParams(
tun: null == tun ? _self.tun : tun // ignore: cast_nullable_to_non_nullable
as Tun,mixedPort: null == mixedPort ? _self.mixedPort : mixedPort // ignore: cast_nullable_to_non_nullable
@@ -365,7 +365,8 @@ as ExternalControllerStatus,unifiedDelay: null == unifiedDelay ? _self.unifiedDe
as bool,authentication: null == authentication ? _self.authentication : authentication // ignore: cast_nullable_to_non_nullable
as List<String>,geoAutoUpdate: null == geoAutoUpdate ? _self.geoAutoUpdate : geoAutoUpdate // ignore: cast_nullable_to_non_nullable
as bool,geoUpdateInterval: null == geoUpdateInterval ? _self.geoUpdateInterval : geoUpdateInterval // ignore: cast_nullable_to_non_nullable
as int,
as int,geoXUrl: null == geoXUrl ? _self.geoXUrl : geoXUrl // ignore: cast_nullable_to_non_nullable
as Map<String, String>,
));
}
/// Create a copy of UpdateParams
@@ -459,10 +460,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( Tun tun, @JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, @JsonKey(name: 'unified-delay') bool unifiedDelay, List<String> authentication, @JsonKey(name: 'geo-auto-update') bool geoAutoUpdate, @JsonKey(name: 'geo-update-interval') int geoUpdateInterval)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( Tun tun, @JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, @JsonKey(name: 'unified-delay') bool unifiedDelay, List<String> authentication, @JsonKey(name: 'geo-auto-update') bool geoAutoUpdate, @JsonKey(name: 'geo-update-interval') int geoUpdateInterval, @JsonKey(name: 'geox-url') Map<String, String> geoXUrl)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _UpdateParams() when $default != null:
return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_that.mode,_that.logLevel,_that.ipv6,_that.tcpConcurrent,_that.externalController,_that.unifiedDelay,_that.authentication,_that.geoAutoUpdate,_that.geoUpdateInterval);case _:
return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_that.mode,_that.logLevel,_that.ipv6,_that.tcpConcurrent,_that.externalController,_that.unifiedDelay,_that.authentication,_that.geoAutoUpdate,_that.geoUpdateInterval,_that.geoXUrl);case _:
return orElse();
}
@@ -480,10 +481,10 @@ return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( Tun tun, @JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, @JsonKey(name: 'unified-delay') bool unifiedDelay, List<String> authentication, @JsonKey(name: 'geo-auto-update') bool geoAutoUpdate, @JsonKey(name: 'geo-update-interval') int geoUpdateInterval) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( Tun tun, @JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, @JsonKey(name: 'unified-delay') bool unifiedDelay, List<String> authentication, @JsonKey(name: 'geo-auto-update') bool geoAutoUpdate, @JsonKey(name: 'geo-update-interval') int geoUpdateInterval, @JsonKey(name: 'geox-url') Map<String, String> geoXUrl) $default,) {final _that = this;
switch (_that) {
case _UpdateParams():
return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_that.mode,_that.logLevel,_that.ipv6,_that.tcpConcurrent,_that.externalController,_that.unifiedDelay,_that.authentication,_that.geoAutoUpdate,_that.geoUpdateInterval);case _:
return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_that.mode,_that.logLevel,_that.ipv6,_that.tcpConcurrent,_that.externalController,_that.unifiedDelay,_that.authentication,_that.geoAutoUpdate,_that.geoUpdateInterval,_that.geoXUrl);case _:
throw StateError('Unexpected subclass');
}
@@ -500,10 +501,10 @@ return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( Tun tun, @JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, @JsonKey(name: 'unified-delay') bool unifiedDelay, List<String> authentication, @JsonKey(name: 'geo-auto-update') bool geoAutoUpdate, @JsonKey(name: 'geo-update-interval') int geoUpdateInterval)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( Tun tun, @JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, @JsonKey(name: 'unified-delay') bool unifiedDelay, List<String> authentication, @JsonKey(name: 'geo-auto-update') bool geoAutoUpdate, @JsonKey(name: 'geo-update-interval') int geoUpdateInterval, @JsonKey(name: 'geox-url') Map<String, String> geoXUrl)? $default,) {final _that = this;
switch (_that) {
case _UpdateParams() when $default != null:
return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_that.mode,_that.logLevel,_that.ipv6,_that.tcpConcurrent,_that.externalController,_that.unifiedDelay,_that.authentication,_that.geoAutoUpdate,_that.geoUpdateInterval);case _:
return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_that.mode,_that.logLevel,_that.ipv6,_that.tcpConcurrent,_that.externalController,_that.unifiedDelay,_that.authentication,_that.geoAutoUpdate,_that.geoUpdateInterval,_that.geoXUrl);case _:
return null;
}
@@ -515,7 +516,7 @@ return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_
@JsonSerializable()
class _UpdateParams implements UpdateParams {
const _UpdateParams({required this.tun, @JsonKey(name: 'mixed-port') required this.mixedPort, @JsonKey(name: 'allow-lan') required this.allowLan, @JsonKey(name: 'find-process-mode') required this.findProcessMode, required this.mode, @JsonKey(name: 'log-level') required this.logLevel, required this.ipv6, @JsonKey(name: 'tcp-concurrent') required this.tcpConcurrent, @JsonKey(name: 'external-controller') required this.externalController, @JsonKey(name: 'unified-delay') required this.unifiedDelay, List<String> authentication = const [], @JsonKey(name: 'geo-auto-update') this.geoAutoUpdate = false, @JsonKey(name: 'geo-update-interval') this.geoUpdateInterval = 24}): _authentication = authentication;
const _UpdateParams({required this.tun, @JsonKey(name: 'mixed-port') required this.mixedPort, @JsonKey(name: 'allow-lan') required this.allowLan, @JsonKey(name: 'find-process-mode') required this.findProcessMode, required this.mode, @JsonKey(name: 'log-level') required this.logLevel, required this.ipv6, @JsonKey(name: 'tcp-concurrent') required this.tcpConcurrent, @JsonKey(name: 'external-controller') required this.externalController, @JsonKey(name: 'unified-delay') required this.unifiedDelay, List<String> authentication = const [], @JsonKey(name: 'geo-auto-update') this.geoAutoUpdate = false, @JsonKey(name: 'geo-update-interval') this.geoUpdateInterval = 24, @JsonKey(name: 'geox-url') Map<String, String> geoXUrl = const {}}): _authentication = authentication,_geoXUrl = geoXUrl;
factory _UpdateParams.fromJson(Map<String, dynamic> json) => _$UpdateParamsFromJson(json);
@override final Tun tun;
@@ -537,6 +538,13 @@ class _UpdateParams implements UpdateParams {
@override@JsonKey(name: 'geo-auto-update') final bool geoAutoUpdate;
@override@JsonKey(name: 'geo-update-interval') final int geoUpdateInterval;
final Map<String, String> _geoXUrl;
@override@JsonKey(name: 'geox-url') Map<String, String> get geoXUrl {
if (_geoXUrl is EqualUnmodifiableMapView) return _geoXUrl;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_geoXUrl);
}
/// Create a copy of UpdateParams
/// with the given fields replaced by the non-null parameter values.
@@ -551,18 +559,18 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _UpdateParams&&(identical(other.tun, tun) || other.tun == tun)&&(identical(other.mixedPort, mixedPort) || other.mixedPort == mixedPort)&&(identical(other.allowLan, allowLan) || other.allowLan == allowLan)&&(identical(other.findProcessMode, findProcessMode) || other.findProcessMode == findProcessMode)&&(identical(other.mode, mode) || other.mode == mode)&&(identical(other.logLevel, logLevel) || other.logLevel == logLevel)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.tcpConcurrent, tcpConcurrent) || other.tcpConcurrent == tcpConcurrent)&&(identical(other.externalController, externalController) || other.externalController == externalController)&&(identical(other.unifiedDelay, unifiedDelay) || other.unifiedDelay == unifiedDelay)&&const DeepCollectionEquality().equals(other.authentication, _authentication)&&(identical(other.geoAutoUpdate, geoAutoUpdate) || other.geoAutoUpdate == geoAutoUpdate)&&(identical(other.geoUpdateInterval, geoUpdateInterval) || other.geoUpdateInterval == geoUpdateInterval));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _UpdateParams&&(identical(other.tun, tun) || other.tun == tun)&&(identical(other.mixedPort, mixedPort) || other.mixedPort == mixedPort)&&(identical(other.allowLan, allowLan) || other.allowLan == allowLan)&&(identical(other.findProcessMode, findProcessMode) || other.findProcessMode == findProcessMode)&&(identical(other.mode, mode) || other.mode == mode)&&(identical(other.logLevel, logLevel) || other.logLevel == logLevel)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.tcpConcurrent, tcpConcurrent) || other.tcpConcurrent == tcpConcurrent)&&(identical(other.externalController, externalController) || other.externalController == externalController)&&(identical(other.unifiedDelay, unifiedDelay) || other.unifiedDelay == unifiedDelay)&&const DeepCollectionEquality().equals(other.authentication, _authentication)&&(identical(other.geoAutoUpdate, geoAutoUpdate) || other.geoAutoUpdate == geoAutoUpdate)&&(identical(other.geoUpdateInterval, geoUpdateInterval) || other.geoUpdateInterval == geoUpdateInterval)&&const DeepCollectionEquality().equals(other.geoXUrl, _geoXUrl));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode {
return Object.hash(runtimeType,tun,mixedPort,allowLan,findProcessMode,mode,logLevel,ipv6,tcpConcurrent,externalController,unifiedDelay,const DeepCollectionEquality().hash(_authentication),geoAutoUpdate,geoUpdateInterval);
return Object.hash(runtimeType,tun,mixedPort,allowLan,findProcessMode,mode,logLevel,ipv6,tcpConcurrent,externalController,unifiedDelay,const DeepCollectionEquality().hash(_authentication),geoAutoUpdate,geoUpdateInterval,const DeepCollectionEquality().hash(_geoXUrl));
}
@override
String toString() {
return 'UpdateParams(tun: $tun, mixedPort: $mixedPort, allowLan: $allowLan, findProcessMode: $findProcessMode, mode: $mode, logLevel: $logLevel, ipv6: $ipv6, tcpConcurrent: $tcpConcurrent, externalController: $externalController, unifiedDelay: $unifiedDelay, authentication: $authentication, geoAutoUpdate: $geoAutoUpdate, geoUpdateInterval: $geoUpdateInterval)';
return 'UpdateParams(tun: $tun, mixedPort: $mixedPort, allowLan: $allowLan, findProcessMode: $findProcessMode, mode: $mode, logLevel: $logLevel, ipv6: $ipv6, tcpConcurrent: $tcpConcurrent, externalController: $externalController, unifiedDelay: $unifiedDelay, authentication: $authentication, geoAutoUpdate: $geoAutoUpdate, geoUpdateInterval: $geoUpdateInterval, geoXUrl: $geoXUrl)';
}
@@ -573,7 +581,7 @@ abstract mixin class _$UpdateParamsCopyWith<$Res> implements $UpdateParamsCopyWi
factory _$UpdateParamsCopyWith(_UpdateParams value, $Res Function(_UpdateParams) _then) = __$UpdateParamsCopyWithImpl;
@override @useResult
$Res call({
Tun tun,@JsonKey(name: 'mixed-port') int mixedPort,@JsonKey(name: 'allow-lan') bool allowLan,@JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode,@JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6,@JsonKey(name: 'tcp-concurrent') bool tcpConcurrent,@JsonKey(name: 'external-controller') ExternalControllerStatus externalController,@JsonKey(name: 'unified-delay') bool unifiedDelay, List<String> authentication,@JsonKey(name: 'geo-auto-update') bool geoAutoUpdate,@JsonKey(name: 'geo-update-interval') int geoUpdateInterval
Tun tun,@JsonKey(name: 'mixed-port') int mixedPort,@JsonKey(name: 'allow-lan') bool allowLan,@JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode,@JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6,@JsonKey(name: 'tcp-concurrent') bool tcpConcurrent,@JsonKey(name: 'external-controller') ExternalControllerStatus externalController,@JsonKey(name: 'unified-delay') bool unifiedDelay, List<String> authentication,@JsonKey(name: 'geo-auto-update') bool geoAutoUpdate,@JsonKey(name: 'geo-update-interval') int geoUpdateInterval,@JsonKey(name: 'geox-url') Map<String, String> geoXUrl
});
@@ -590,7 +598,7 @@ class __$UpdateParamsCopyWithImpl<$Res>
/// Create a copy of UpdateParams
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? tun = null,Object? mixedPort = null,Object? allowLan = null,Object? findProcessMode = null,Object? mode = null,Object? logLevel = null,Object? ipv6 = null,Object? tcpConcurrent = null,Object? externalController = null,Object? unifiedDelay = null,Object? authentication = null,Object? geoAutoUpdate = null,Object? geoUpdateInterval = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? tun = null,Object? mixedPort = null,Object? allowLan = null,Object? findProcessMode = null,Object? mode = null,Object? logLevel = null,Object? ipv6 = null,Object? tcpConcurrent = null,Object? externalController = null,Object? unifiedDelay = null,Object? authentication = null,Object? geoAutoUpdate = null,Object? geoUpdateInterval = null,Object? geoXUrl = null,}) {
return _then(_UpdateParams(
tun: null == tun ? _self.tun : tun // ignore: cast_nullable_to_non_nullable
as Tun,mixedPort: null == mixedPort ? _self.mixedPort : mixedPort // ignore: cast_nullable_to_non_nullable
@@ -605,7 +613,8 @@ as ExternalControllerStatus,unifiedDelay: null == unifiedDelay ? _self.unifiedDe
as bool,authentication: null == authentication ? _self._authentication : authentication // ignore: cast_nullable_to_non_nullable
as List<String>,geoAutoUpdate: null == geoAutoUpdate ? _self.geoAutoUpdate : geoAutoUpdate // ignore: cast_nullable_to_non_nullable
as bool,geoUpdateInterval: null == geoUpdateInterval ? _self.geoUpdateInterval : geoUpdateInterval // ignore: cast_nullable_to_non_nullable
as int,
as int,geoXUrl: null == geoXUrl ? _self._geoXUrl : geoXUrl // ignore: cast_nullable_to_non_nullable
as Map<String, String>,
));
}
+6
View File
@@ -42,6 +42,11 @@ _UpdateParams _$UpdateParamsFromJson(Map<String, dynamic> json) =>
const [],
geoAutoUpdate: json['geo-auto-update'] as bool? ?? false,
geoUpdateInterval: (json['geo-update-interval'] as num?)?.toInt() ?? 24,
geoXUrl:
(json['geox-url'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
) ??
const {},
);
Map<String, dynamic> _$UpdateParamsToJson(_UpdateParams instance) =>
@@ -60,6 +65,7 @@ Map<String, dynamic> _$UpdateParamsToJson(_UpdateParams instance) =>
'authentication': instance.authentication,
'geo-auto-update': instance.geoAutoUpdate,
'geo-update-interval': instance.geoUpdateInterval,
'geox-url': instance.geoXUrl,
};
const _$FindProcessModeEnumMap = {
+15
View File
@@ -89,6 +89,21 @@ void main() {
expect(config.geoXUrl, {GeoResource.MMDB: 'https://example.com/mmdb'});
});
test('toUpdateParams sends geoXUrl to the Core under geox-url', () {
final params = const PatchClashConfig(
geoXUrl: {
GeoResource.MMDB: 'https://example.com/geoip.metadb',
GeoResource.GEOSITE: 'https://example.com/geosite.dat',
},
).toUpdateParams(routeMode: RouteMode.config, authentication: const []);
final json = jsonDecode(jsonEncode(params)) as Map<String, Object?>;
expect(json['geox-url'], {
'mmdb': 'https://example.com/geoip.metadb',
'geosite': 'https://example.com/geosite.dat',
});
});
});
group('AppSettingProps JSON round-trip', () {