Files
MWClash/core/hub.go
chen08209 bee2f8aa4f Optimize provider page
Optimize delay test

Support local backup and recovery
2024-12-06 19:24:11 +08:00

527 lines
12 KiB
Go

package main
/*
#include <stdlib.h>
*/
import "C"
import (
bridge "core/dart-bridge"
"encoding/json"
"fmt"
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/outboundgroup"
"github.com/metacubex/mihomo/adapter/provider"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/updater"
"github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant"
cp "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/hub/executor"
"github.com/metacubex/mihomo/log"
rp "github.com/metacubex/mihomo/rules/provider"
"github.com/metacubex/mihomo/tunnel"
"github.com/metacubex/mihomo/tunnel/statistic"
"golang.org/x/net/context"
"os"
"runtime"
"time"
"unsafe"
)
var currentConfig = config.DefaultRawConfig()
var configParams = ConfigExtendedParams{}
var isInit = false
//export initClash
func initClash(homeDirStr *C.char) bool {
if !isInit {
constant.SetHomeDir(C.GoString(homeDirStr))
isInit = true
}
return isInit
}
//export getIsInit
func getIsInit() bool {
return isInit
}
//export restartClash
func restartClash() bool {
execPath, _ := os.Executable()
go restartExecutable(execPath)
return true
}
//export shutdownClash
func shutdownClash() bool {
executor.Shutdown()
runtime.GC()
isInit = false
currentConfig = nil
return true
}
//export forceGc
func forceGc() {
go func() {
log.Infoln("[APP] request force GC")
runtime.GC()
}()
}
//export validateConfig
func validateConfig(s *C.char, port C.longlong) {
i := int64(port)
bytes := []byte(C.GoString(s))
go func() {
_, err := config.UnmarshalRawConfig(bytes)
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
bridge.SendToPort(i, "")
}()
}
//export updateConfig
func updateConfig(s *C.char, port C.longlong) {
i := int64(port)
paramsString := C.GoString(s)
go func() {
var params = &GenerateConfigParams{}
err := json.Unmarshal([]byte(paramsString), params)
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
configParams = params.Params
prof := decorationConfig(params.ProfileId, params.Config)
currentConfig = prof
err = applyConfig()
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
bridge.SendToPort(i, "")
}()
}
//export clearEffect
func clearEffect(s *C.char) {
id := C.GoString(s)
go func() {
_ = removeFile(getProfilePath(id))
_ = removeFile(getProfileProvidersPath(id))
}()
}
//export getProxies
func getProxies() *C.char {
data, err := json.Marshal(tunnel.ProxiesWithProviders())
if err != nil {
return C.CString("")
}
return C.CString(string(data))
}
//export changeProxy
func changeProxy(s *C.char) {
paramsString := C.GoString(s)
var params = &ChangeProxyParams{}
err := json.Unmarshal([]byte(paramsString), params)
if err != nil {
log.Infoln("Unmarshal ChangeProxyParams %v", err)
}
groupName := *params.GroupName
proxyName := *params.ProxyName
proxies := tunnel.ProxiesWithProviders()
group, ok := proxies[groupName]
if !ok {
return
}
adapterProxy := group.(*adapter.Proxy)
selector, ok := adapterProxy.ProxyAdapter.(outboundgroup.SelectAble)
if !ok {
return
}
if proxyName == "" {
selector.ForceSet(proxyName)
} else {
err = selector.Set(proxyName)
}
if err == nil {
log.Infoln("[SelectAble] %s selected %s", groupName, proxyName)
}
}
//export getTraffic
func getTraffic() *C.char {
up, down := statistic.DefaultManager.Current(state.OnlyProxy)
traffic := map[string]int64{
"up": up,
"down": down,
}
data, err := json.Marshal(traffic)
if err != nil {
fmt.Println("Error:", err)
return C.CString("")
}
return C.CString(string(data))
}
//export getTotalTraffic
func getTotalTraffic() *C.char {
up, down := statistic.DefaultManager.Total(state.OnlyProxy)
traffic := map[string]int64{
"up": up,
"down": down,
}
data, err := json.Marshal(traffic)
if err != nil {
fmt.Println("Error:", err)
return C.CString("")
}
return C.CString(string(data))
}
//export resetTraffic
func resetTraffic() {
statistic.DefaultManager.ResetStatistic()
}
//export asyncTestDelay
func asyncTestDelay(s *C.char, port C.longlong) {
i := int64(port)
paramsString := C.GoString(s)
b.Go(paramsString, func() (bool, error) {
var params = &TestDelayParams{}
err := json.Unmarshal([]byte(paramsString), params)
if err != nil {
return false, nil
}
expectedStatus, err := utils.NewUnsignedRanges[uint16]("")
if err != nil {
return false, nil
}
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(params.Timeout))
defer cancel()
proxies := tunnel.ProxiesWithProviders()
proxy := proxies[params.ProxyName]
delayData := &Delay{
Name: params.ProxyName,
}
if proxy == nil {
delayData.Value = -1
data, _ := json.Marshal(delayData)
bridge.SendToPort(i, string(data))
return false, nil
}
delay, err := proxy.URLTest(ctx, constant.DefaultTestURL, expectedStatus)
if err != nil || delay == 0 {
delayData.Value = -1
data, _ := json.Marshal(delayData)
bridge.SendToPort(i, string(data))
return false, nil
}
delayData.Value = int32(delay)
data, _ := json.Marshal(delayData)
bridge.SendToPort(i, string(data))
return false, nil
})
}
//export getVersionInfo
func getVersionInfo() *C.char {
versionInfo := map[string]string{
"clashName": constant.Name,
"version": "1.18.5",
}
data, err := json.Marshal(versionInfo)
if err != nil {
fmt.Println("Error:", err)
return C.CString("")
}
return C.CString(string(data))
}
//export getConnections
func getConnections() *C.char {
snapshot := statistic.DefaultManager.Snapshot()
data, err := json.Marshal(snapshot)
if err != nil {
fmt.Println("Error:", err)
return C.CString("")
}
return C.CString(string(data))
}
//export closeConnections
func closeConnections() {
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
err := c.Close()
if err != nil {
return false
}
return true
})
}
//export closeConnection
func closeConnection(id *C.char) {
connectionId := C.GoString(id)
c := statistic.DefaultManager.Get(connectionId)
if c == nil {
return
}
_ = c.Close()
}
//export getProviders
func getProviders() *C.char {
data, err := json.Marshal(tunnel.Providers())
var msg *C.char
if err != nil {
msg = C.CString("")
return msg
}
msg = C.CString(string(data))
return msg
}
//export getProvider
func getProvider(name *C.char) *C.char {
providerName := C.GoString(name)
providers := tunnel.Providers()
data, err := json.Marshal(providers[providerName])
if err != nil {
return C.CString("")
}
return C.CString(string(data))
}
//export getExternalProviders
func getExternalProviders() *C.char {
externalProviders := make(map[string]ExternalProvider)
for n, p := range tunnel.Providers() {
if p.VehicleType() != cp.Compatible {
p := p.(*provider.ProxySetProvider)
externalProviders[n] = ExternalProvider{
Name: n,
Type: p.Type().String(),
VehicleType: p.VehicleType().String(),
Count: p.Count(),
Path: p.Vehicle().Path(),
UpdateAt: p.UpdatedAt,
}
}
}
for n, p := range tunnel.RuleProviders() {
if p.VehicleType() != cp.Compatible {
p := p.(*rp.RuleSetProvider)
externalProviders[n] = ExternalProvider{
Name: n,
Type: p.Type().String(),
VehicleType: p.VehicleType().String(),
Count: p.Count(),
Path: p.Vehicle().Path(),
UpdateAt: p.UpdatedAt,
}
}
}
data, err := json.Marshal(externalProviders)
if err != nil {
return C.CString("")
}
return C.CString(string(data))
}
//export getExternalProvider
func getExternalProvider(name *C.char) *C.char {
externalProviderName := C.GoString(name)
externalProviders := getExternalProvidersRaw()
externalProvider, exist := externalProviders[externalProviderName]
if !exist {
return C.CString("")
}
data, err := json.Marshal(externalProvider)
if err != nil {
return C.CString("")
}
return C.CString(string(data))
}
//export updateExternalProvider
func updateExternalProvider(providerName *C.char, providerType *C.char, port C.longlong) {
i := int64(port)
providerNameString := C.GoString(providerName)
providerTypeString := C.GoString(providerType)
go func() {
switch providerTypeString {
case "Proxy":
providers := tunnel.Providers()
proxyProvider, exist := providers[providerNameString].(*provider.ProxySetProvider)
if !exist {
bridge.SendToPort(i, "proxy provider is not exist")
return
}
err := proxyProvider.Update()
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
case "Rule":
providers := tunnel.RuleProviders()
ruleProvider, exist := providers[providerNameString].(*rp.RuleSetProvider)
if !exist {
bridge.SendToPort(i, "rule provider is not exist")
return
}
err := ruleProvider.Update()
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
case "MMDB":
err := updater.UpdateMMDB(constant.Path.Resolve(providerNameString))
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
case "ASN":
err := updater.UpdateASN(constant.Path.Resolve(providerNameString))
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
case "GeoIp":
err := updater.UpdateGeoIp(constant.Path.Resolve(providerNameString))
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
case "GeoSite":
err := updater.UpdateGeoSite(constant.Path.Resolve(providerNameString))
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
}
bridge.SendToPort(i, "")
}()
}
//func sideLoadExternalProvider(providerName *C.char, providerType *C.char, data *C.char, port C.longlong) {
// i := int64(port)
// bytes := []byte(C.GoString(data))
// providerNameString := C.GoString(providerName)
// providerTypeString := C.GoString(providerType)
// go func() {
// switch providerTypeString {
// case "Proxy":
// providers := tunnel.Providers()
// proxyProvider, exist := providers[providerNameString].(*provider.ProxySetProvider)
// if exist {
// bridge.SendToPort(i, "proxy provider is not exist")
// return
// }
// err := proxyProvider.Update()
// if err != nil {
// bridge.SendToPort(i, err.Error())
// return
// }
// case "Rule":
// providers := tunnel.RuleProviders()
// ruleProvider, exist := providers[providerNameString].(*rp.RuleSetProvider)
// if exist {
// bridge.SendToPort(i, "proxy provider is not exist")
// return
// }
// err := ruleProvider.Update()
// if err != nil {
// bridge.SendToPort(i, err.Error())
// return
// }
// case "MMDB":
// err := updater.UpdateMMDB(constant.Path.Resolve(providerNameString))
// if err != nil {
// bridge.SendToPort(i, err.Error())
// return
// }
// case "ASN":
// err := updater.UpdateASN(constant.Path.Resolve(providerNameString))
// if err != nil {
// bridge.SendToPort(i, err.Error())
// return
// }
// case "GeoIp":
// err := updater.UpdateGeoIp(constant.Path.Resolve(providerNameString))
// if err != nil {
// bridge.SendToPort(i, err.Error())
// return
// }
// case "GeoSite":
// err := updater.UpdateGeoSite(constant.Path.Resolve(providerNameString))
// if err != nil {
// bridge.SendToPort(i, err.Error())
// return
// }
// }
// bridge.SendToPort(i, "")
// }()
//}
//export initNativeApiBridge
func initNativeApiBridge(api unsafe.Pointer) {
bridge.InitDartApi(api)
}
//export initMessage
func initMessage(port C.longlong) {
i := int64(port)
Port = i
}
//export freeCString
func freeCString(s *C.char) {
C.free(unsafe.Pointer(s))
}
func init() {
provider.HealthcheckHook = func(name string, delay uint16) {
delayData := &Delay{
Name: name,
}
if delay == 0 {
delayData.Value = -1
} else {
delayData.Value = int32(delay)
}
SendMessage(Message{
Type: DelayMessage,
Data: delayData,
})
}
statistic.DefaultRequestNotify = func(c statistic.Tracker) {
SendMessage(Message{
Type: RequestMessage,
Data: c,
})
}
executor.DefaultProviderLoadedHook = func(providerName string) {
SendMessage(Message{
Type: LoadedMessage,
Data: providerName,
})
}
}