Update healthcheck policy
This commit is contained in:
Submodule core/Clash.Meta updated: eaa996819d...d1dc1e4433
@@ -7,14 +7,17 @@ import (
|
||||
"github.com/metacubex/mihomo/component/process"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/config"
|
||||
"github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/dns"
|
||||
"github.com/metacubex/mihomo/hub/executor"
|
||||
"github.com/metacubex/mihomo/listener"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
@@ -170,6 +173,29 @@ func patchConfig(general *config.General) {
|
||||
resolver.DisableIPv6 = !general.IPv6
|
||||
}
|
||||
|
||||
const concurrentCount = math.MaxInt
|
||||
|
||||
func hcCompatibleProvider(proxyProviders map[string]provider.ProxyProvider) {
|
||||
wg := sync.WaitGroup{}
|
||||
ch := make(chan struct{}, concurrentCount)
|
||||
for _, proxyProvider := range proxyProviders {
|
||||
proxyProvider := proxyProvider
|
||||
if proxyProvider.VehicleType() == provider.Compatible {
|
||||
log.Infoln("Start initial Compatible provider %s", proxyProvider.Name())
|
||||
wg.Add(1)
|
||||
ch <- struct{}{}
|
||||
go func() {
|
||||
defer func() { <-ch; wg.Done() }()
|
||||
if err := proxyProvider.Initial(); err != nil {
|
||||
log.Errorln("initial Compatible provider %s error: %v", proxyProvider.Name(), err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func applyConfig(isPatch bool) {
|
||||
cfg, err := config.ParseRawConfig(currentConfig)
|
||||
if err != nil {
|
||||
|
||||
14
core/hub.go
14
core/hub.go
@@ -251,6 +251,11 @@ func getProvider(name *C.char) *C.char {
|
||||
return C.CString(string(data))
|
||||
}
|
||||
|
||||
//export healthcheck
|
||||
func healthcheck() {
|
||||
hcCompatibleProvider(tunnel.Providers())
|
||||
}
|
||||
|
||||
//export initNativeApiBridge
|
||||
func initNativeApiBridge(api unsafe.Pointer, port C.longlong) {
|
||||
bridge.InitDartApi(api)
|
||||
@@ -273,13 +278,4 @@ func init() {
|
||||
Data: delayData,
|
||||
})
|
||||
}
|
||||
adapter.NowChangeHook = func(name, value string) {
|
||||
bridge.SendMessage(bridge.Message{
|
||||
Type: bridge.Now,
|
||||
Data: Now{
|
||||
Name: name,
|
||||
Value: value,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'package:fl_clash/l10n/l10n.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
|
||||
@@ -124,6 +124,10 @@ class ClashCore {
|
||||
return true;
|
||||
}
|
||||
|
||||
healthcheck(){
|
||||
clashFFI.healthcheck();
|
||||
}
|
||||
|
||||
VersionInfo getVersionInfo() {
|
||||
final versionInfoRaw = clashFFI.getVersionInfo();
|
||||
final versionInfo = json.decode(versionInfoRaw.cast<Utf8>().toDartString());
|
||||
|
||||
@@ -1040,6 +1040,14 @@ class ClashFFI {
|
||||
late final _getProvider = _getProviderPtr
|
||||
.asFunction<ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>();
|
||||
|
||||
void healthcheck() {
|
||||
return _healthcheck();
|
||||
}
|
||||
|
||||
late final _healthcheckPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function()>>('healthcheck');
|
||||
late final _healthcheck = _healthcheckPtr.asFunction<void Function()>();
|
||||
|
||||
void initNativeApiBridge(
|
||||
ffi.Pointer<ffi.Void> api,
|
||||
int port,
|
||||
|
||||
@@ -56,9 +56,7 @@ class AppController {
|
||||
updateRunTime() {
|
||||
if (proxyManager.startTime != null) {
|
||||
final startTimeStamp = proxyManager.startTime!.millisecondsSinceEpoch;
|
||||
final nowTimeStamp = DateTime
|
||||
.now()
|
||||
.millisecondsSinceEpoch;
|
||||
final nowTimeStamp = DateTime.now().millisecondsSinceEpoch;
|
||||
appState.runTime = nowTimeStamp - startTimeStamp;
|
||||
} else {
|
||||
appState.runTime = null;
|
||||
@@ -133,7 +131,7 @@ class AppController {
|
||||
);
|
||||
}
|
||||
|
||||
Function? _changeProfileDebounce;
|
||||
Function? _changeProfileDebounce;
|
||||
|
||||
changeProfileDebounce(String? profileId) {
|
||||
if (profileId == config.currentProfileId) return;
|
||||
@@ -159,8 +157,8 @@ class AppController {
|
||||
if (!profile.autoUpdate) return;
|
||||
final isNotNeedUpdate = profile.lastUpdateDate
|
||||
?.add(
|
||||
profile.autoUpdateDuration,
|
||||
)
|
||||
profile.autoUpdateDuration,
|
||||
)
|
||||
.isBeforeNow();
|
||||
if (isNotNeedUpdate == false) continue;
|
||||
await profile.update();
|
||||
@@ -177,7 +175,7 @@ class AppController {
|
||||
|
||||
clearCurrentDelay() {
|
||||
final currentProxyName =
|
||||
appState.getCurrentProxyName(config.currentProxyName, clashConfig.mode);
|
||||
appState.getCurrentProxyName(config.currentProxyName, clashConfig.mode);
|
||||
if (currentProxyName == null) return;
|
||||
appState.setDelay(Delay(name: currentProxyName, value: null));
|
||||
}
|
||||
@@ -225,19 +223,30 @@ class AppController {
|
||||
}
|
||||
|
||||
afterInit() async {
|
||||
if (appState.isInit) {
|
||||
if (config.autoRun) {
|
||||
await updateSystemProxy(true);
|
||||
} else {
|
||||
await proxyManager.updateStartTime();
|
||||
await updateSystemProxy(proxyManager.isStart);
|
||||
}
|
||||
autoUpdateProfiles();
|
||||
updateLogStatus();
|
||||
if (!config.silentLaunch) {
|
||||
window?.show();
|
||||
}
|
||||
if (config.autoRun) {
|
||||
await updateSystemProxy(true);
|
||||
} else {
|
||||
await proxyManager.updateStartTime();
|
||||
await updateSystemProxy(proxyManager.isStart);
|
||||
}
|
||||
autoUpdateProfiles();
|
||||
updateLogStatus();
|
||||
if (!config.silentLaunch) {
|
||||
window?.show();
|
||||
}
|
||||
periodicUpdateGroups();
|
||||
}
|
||||
|
||||
periodicUpdateGroups() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (globalState.updateGroupsTimer != null) {
|
||||
globalState.updateGroupsTimer!.cancel();
|
||||
}
|
||||
globalState.updateGroupsTimer =
|
||||
Timer.periodic(const Duration(seconds: 5), (Timer t) {
|
||||
updateGroups();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setDelay(Delay delay) {
|
||||
@@ -264,7 +273,7 @@ class AppController {
|
||||
|
||||
toProfiles() {
|
||||
final index = globalState.currentNavigationItems.indexWhere(
|
||||
(element) => element.label == "profiles",
|
||||
(element) => element.label == "profiles",
|
||||
);
|
||||
if (index != -1) {
|
||||
toPage(index);
|
||||
@@ -277,7 +286,7 @@ class AppController {
|
||||
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
|
||||
if (commonScaffoldState?.mounted != true) return;
|
||||
commonScaffoldState?.loadingRun(
|
||||
() async {
|
||||
() async {
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
final profile = Profile(
|
||||
url: url,
|
||||
@@ -298,7 +307,7 @@ class AppController {
|
||||
|
||||
initLink() {
|
||||
linkManager.initAppLinksListen(
|
||||
(url) {
|
||||
(url) {
|
||||
globalState.showMessage(
|
||||
title: "${appLocalizations.add}${appLocalizations.profile}",
|
||||
message: TextSpan(
|
||||
@@ -307,20 +316,14 @@ class AppController {
|
||||
TextSpan(
|
||||
text: " $url ",
|
||||
style: TextStyle(
|
||||
color: Theme
|
||||
.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: Theme
|
||||
.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
decorationColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text:
|
||||
"${appLocalizations.create}${appLocalizations.profile}"),
|
||||
"${appLocalizations.create}${appLocalizations.profile}"),
|
||||
],
|
||||
),
|
||||
onTab: () {
|
||||
@@ -340,7 +343,7 @@ class AppController {
|
||||
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
|
||||
if (commonScaffoldState?.mounted != true) return;
|
||||
commonScaffoldState?.loadingRun(
|
||||
() async {
|
||||
() async {
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
final bytes = result.data?.bytes;
|
||||
if (bytes == null) {
|
||||
|
||||
@@ -83,11 +83,7 @@ class _ProxiesFragmentState extends State<ProxiesFragment>
|
||||
);
|
||||
},
|
||||
builder: (_, state, __) {
|
||||
if (_tabController != null) {
|
||||
_tabController!.dispose();
|
||||
_tabController = null;
|
||||
}
|
||||
_tabController = TabController(
|
||||
_tabController ??= TabController(
|
||||
length: state.groups.length,
|
||||
vsync: this,
|
||||
initialIndex: state.currentIndex,
|
||||
@@ -290,7 +286,7 @@ class _ProxiesTabViewState extends State<ProxiesTabView> {
|
||||
}
|
||||
|
||||
_buildGrid({
|
||||
required ProxiesSortType proxiesSortType,
|
||||
required List<Proxy> proxies,
|
||||
required int columns,
|
||||
}) {
|
||||
return SingleChildScrollView(
|
||||
@@ -309,7 +305,7 @@ class _ProxiesTabViewState extends State<ProxiesTabView> {
|
||||
),
|
||||
builder: (_, state, __) {
|
||||
return AnimateGrid<Proxy>(
|
||||
items: _getProxies(group.all, proxiesSortType),
|
||||
items: proxies,
|
||||
columns: columns,
|
||||
itemHeight: _getItemHeight(),
|
||||
keyBuilder: (item) {
|
||||
@@ -342,9 +338,13 @@ class _ProxiesTabViewState extends State<ProxiesTabView> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<Config, ProxiesSortType>(
|
||||
selector: (_, config) => config.proxiesSortType,
|
||||
builder: (_, proxiesSortType, __) {
|
||||
return Selector2<AppState, Config, ProxiesSortSelectorState>(
|
||||
selector: (_, appState, config) => ProxiesSortSelectorState(
|
||||
proxiesSortType: config.proxiesSortType,
|
||||
sortNum: appState.sortNum,
|
||||
),
|
||||
builder: (_, state, __) {
|
||||
final proxies = _getProxies(group.all, state.proxiesSortType);
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: SlotLayout(
|
||||
@@ -352,21 +352,21 @@ class _ProxiesTabViewState extends State<ProxiesTabView> {
|
||||
Breakpoints.small: SlotLayout.from(
|
||||
key: const Key('proxies_grid_small'),
|
||||
builder: (_) => _buildGrid(
|
||||
proxiesSortType: proxiesSortType,
|
||||
proxies: proxies,
|
||||
columns: 2,
|
||||
),
|
||||
),
|
||||
Breakpoints.medium: SlotLayout.from(
|
||||
key: const Key('proxies_grid_medium'),
|
||||
builder: (_) => _buildGrid(
|
||||
proxiesSortType: proxiesSortType,
|
||||
proxies: proxies,
|
||||
columns: 3,
|
||||
),
|
||||
),
|
||||
Breakpoints.large: SlotLayout.from(
|
||||
key: const Key('proxies_grid_large'),
|
||||
builder: (_) => _buildGrid(
|
||||
proxiesSortType: proxiesSortType,
|
||||
proxies: proxies,
|
||||
columns: 4,
|
||||
),
|
||||
),
|
||||
@@ -399,22 +399,17 @@ class _DelayTestButtonContainerState extends State<DelayTestButtonContainer>
|
||||
|
||||
_getDelayMap() async {
|
||||
_controller.forward();
|
||||
// for (final proxy in group.all) {
|
||||
// context.appController.setDelay(
|
||||
// Delay(
|
||||
// name: proxy.name,
|
||||
// value: 0,
|
||||
// ),
|
||||
// );
|
||||
// clashCore.delay(
|
||||
// proxy.name,
|
||||
// );
|
||||
// }
|
||||
for (final delay in context.appController.appState.delayMap.entries) {
|
||||
context.appController.setDelay(Delay(
|
||||
name: delay.key,
|
||||
value: 0,
|
||||
));
|
||||
}
|
||||
clashCore.healthcheck();
|
||||
await Future.delayed(
|
||||
appConstant.httpTimeoutDuration + appConstant.moreDuration,
|
||||
);
|
||||
_controller.reverse();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -22,6 +22,7 @@ class AppState with ChangeNotifier {
|
||||
String _currentLabel;
|
||||
SystemColorSchemes _systemColorSchemes;
|
||||
List<Group> _groups;
|
||||
num _sortNum;
|
||||
|
||||
AppState()
|
||||
: _navigationItems = [],
|
||||
@@ -32,6 +33,7 @@ class AppState with ChangeNotifier {
|
||||
_logs = [],
|
||||
_groups = [],
|
||||
_packages = [],
|
||||
_sortNum = 0,
|
||||
_systemColorSchemes = SystemColorSchemes();
|
||||
|
||||
String get currentLabel => _currentLabel;
|
||||
@@ -159,6 +161,15 @@ class AppState with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
num get sortNum => _sortNum;
|
||||
|
||||
set sortNum(num value) {
|
||||
if (_sortNum != value) {
|
||||
_sortNum = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
List<Group> getCurrentGroups(Mode mode) {
|
||||
switch (mode) {
|
||||
case Mode.direct:
|
||||
|
||||
@@ -2032,3 +2032,145 @@ abstract class _ProxiesCardSelectorState implements ProxiesCardSelectorState {
|
||||
_$$ProxiesCardSelectorStateImplCopyWith<_$ProxiesCardSelectorStateImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ProxiesSortSelectorState {
|
||||
ProxiesSortType get proxiesSortType => throw _privateConstructorUsedError;
|
||||
num get sortNum => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$ProxiesSortSelectorStateCopyWith<ProxiesSortSelectorState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ProxiesSortSelectorStateCopyWith<$Res> {
|
||||
factory $ProxiesSortSelectorStateCopyWith(ProxiesSortSelectorState value,
|
||||
$Res Function(ProxiesSortSelectorState) then) =
|
||||
_$ProxiesSortSelectorStateCopyWithImpl<$Res, ProxiesSortSelectorState>;
|
||||
@useResult
|
||||
$Res call({ProxiesSortType proxiesSortType, num sortNum});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ProxiesSortSelectorStateCopyWithImpl<$Res,
|
||||
$Val extends ProxiesSortSelectorState>
|
||||
implements $ProxiesSortSelectorStateCopyWith<$Res> {
|
||||
_$ProxiesSortSelectorStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? proxiesSortType = null,
|
||||
Object? sortNum = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
proxiesSortType: null == proxiesSortType
|
||||
? _value.proxiesSortType
|
||||
: proxiesSortType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxiesSortType,
|
||||
sortNum: null == sortNum
|
||||
? _value.sortNum
|
||||
: sortNum // ignore: cast_nullable_to_non_nullable
|
||||
as num,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ProxiesSortSelectorStateImplCopyWith<$Res>
|
||||
implements $ProxiesSortSelectorStateCopyWith<$Res> {
|
||||
factory _$$ProxiesSortSelectorStateImplCopyWith(
|
||||
_$ProxiesSortSelectorStateImpl value,
|
||||
$Res Function(_$ProxiesSortSelectorStateImpl) then) =
|
||||
__$$ProxiesSortSelectorStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({ProxiesSortType proxiesSortType, num sortNum});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ProxiesSortSelectorStateImplCopyWithImpl<$Res>
|
||||
extends _$ProxiesSortSelectorStateCopyWithImpl<$Res,
|
||||
_$ProxiesSortSelectorStateImpl>
|
||||
implements _$$ProxiesSortSelectorStateImplCopyWith<$Res> {
|
||||
__$$ProxiesSortSelectorStateImplCopyWithImpl(
|
||||
_$ProxiesSortSelectorStateImpl _value,
|
||||
$Res Function(_$ProxiesSortSelectorStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? proxiesSortType = null,
|
||||
Object? sortNum = null,
|
||||
}) {
|
||||
return _then(_$ProxiesSortSelectorStateImpl(
|
||||
proxiesSortType: null == proxiesSortType
|
||||
? _value.proxiesSortType
|
||||
: proxiesSortType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxiesSortType,
|
||||
sortNum: null == sortNum
|
||||
? _value.sortNum
|
||||
: sortNum // ignore: cast_nullable_to_non_nullable
|
||||
as num,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ProxiesSortSelectorStateImpl implements _ProxiesSortSelectorState {
|
||||
const _$ProxiesSortSelectorStateImpl(
|
||||
{required this.proxiesSortType, required this.sortNum});
|
||||
|
||||
@override
|
||||
final ProxiesSortType proxiesSortType;
|
||||
@override
|
||||
final num sortNum;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProxiesSortSelectorState(proxiesSortType: $proxiesSortType, sortNum: $sortNum)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ProxiesSortSelectorStateImpl &&
|
||||
(identical(other.proxiesSortType, proxiesSortType) ||
|
||||
other.proxiesSortType == proxiesSortType) &&
|
||||
(identical(other.sortNum, sortNum) || other.sortNum == sortNum));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, proxiesSortType, sortNum);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ProxiesSortSelectorStateImplCopyWith<_$ProxiesSortSelectorStateImpl>
|
||||
get copyWith => __$$ProxiesSortSelectorStateImplCopyWithImpl<
|
||||
_$ProxiesSortSelectorStateImpl>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _ProxiesSortSelectorState implements ProxiesSortSelectorState {
|
||||
const factory _ProxiesSortSelectorState(
|
||||
{required final ProxiesSortType proxiesSortType,
|
||||
required final num sortNum}) = _$ProxiesSortSelectorStateImpl;
|
||||
|
||||
@override
|
||||
ProxiesSortType get proxiesSortType;
|
||||
@override
|
||||
num get sortNum;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ProxiesSortSelectorStateImplCopyWith<_$ProxiesSortSelectorStateImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
@@ -118,3 +118,11 @@ class ProxiesCardSelectorState with _$ProxiesCardSelectorState{
|
||||
required String? currentProxyName,
|
||||
}) = _ProxiesCardSelectorState;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProxiesSortSelectorState with _$ProxiesSortSelectorState{
|
||||
const factory ProxiesSortSelectorState({
|
||||
required ProxiesSortType proxiesSortType,
|
||||
required num sortNum,
|
||||
}) = _ProxiesSortSelectorState;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@ import 'common/common.dart';
|
||||
|
||||
class GlobalState {
|
||||
Timer? timer;
|
||||
Timer? currentDelayTimer;
|
||||
Timer? updateGroupsTimer;
|
||||
Function? updateCurrentDelayDebounce;
|
||||
Function? updateSortNumDebounce;
|
||||
PageController? pageController;
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
final Map<int, String?> packageNameMap = {};
|
||||
|
||||
@@ -39,6 +39,15 @@ class _ClashMessageContainerState extends State<ClashMessageContainer>
|
||||
@override
|
||||
void onDelay(Delay delay) {
|
||||
context.appController.setDelay(delay);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
globalState.updateSortNumDebounce ??= debounce<Function()>(
|
||||
() {
|
||||
context.appController.appState.sortNum++;
|
||||
},
|
||||
milliseconds: appConstant.httpTimeoutDuration.inMilliseconds,
|
||||
);
|
||||
globalState.updateSortNumDebounce!();
|
||||
});
|
||||
super.onDelay(delay);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user