From c3d72f6883d991a9dbcbdb9e54dcd838864a07f1 Mon Sep 17 00:00:00 2001 From: Larvan2 <78135608+larvan2@users.noreply.github.com> Date: Fri, 15 Sep 2023 23:25:56 +0800 Subject: [PATCH] feat: download/upgrade XD to external-ui --- config/{updateGeo.go => update_geo.go} | 22 ----- config/update_xd.go | 123 +++++++++++++++++++++++++ config/utils.go | 22 +++++ constant/path.go | 1 + hub/route/server.go | 1 + hub/route/upgrade.go | 17 ++++ hub/updater/updater.go | 2 +- 7 files changed, 165 insertions(+), 23 deletions(-) rename config/{updateGeo.go => update_geo.go} (75%) create mode 100644 config/update_xd.go diff --git a/config/updateGeo.go b/config/update_geo.go similarity index 75% rename from config/updateGeo.go rename to config/update_geo.go index b75d3184..07f211e4 100644 --- a/config/updateGeo.go +++ b/config/update_geo.go @@ -1,17 +1,11 @@ package config import ( - "context" "fmt" - "io" - "net/http" - "os" "runtime" - "time" "github.com/Dreamacro/clash/component/geodata" _ "github.com/Dreamacro/clash/component/geodata/standard" - clashHttp "github.com/Dreamacro/clash/component/http" C "github.com/Dreamacro/clash/constant" "github.com/oschwald/maxminddb-golang" @@ -72,19 +66,3 @@ func UpdateGeoDatabases() error { return nil } - -func downloadForBytes(url string) ([]byte, error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) - defer cancel() - resp, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - return io.ReadAll(resp.Body) -} - -func saveFile(bytes []byte, path string) error { - return os.WriteFile(path, bytes, 0o644) -} diff --git a/config/update_xd.go b/config/update_xd.go new file mode 100644 index 00000000..696768b4 --- /dev/null +++ b/config/update_xd.go @@ -0,0 +1,123 @@ +package config + +import ( + "archive/zip" + "fmt" + "io" + "os" + "path" + "path/filepath" + "strings" + "sync" + + C "github.com/Dreamacro/clash/constant" +) + +const xdURL = "https://codeload.github.com/MetaCubeX/metacubexd/zip/refs/heads/gh-pages" + +var xdMutex sync.Mutex + +func UpdateXD() error { + xdMutex.Lock() + defer xdMutex.Unlock() + + err := cleanup(C.UIPath) + if err != nil { + return fmt.Errorf("cleanup exist file error: %w", err) + } + + data, err := downloadForBytes(xdURL) + if err != nil { + return fmt.Errorf("can't download XD file: %w", err) + } + + saved := path.Join(C.UIPath, "xd.zip") + if saveFile(data, saved) != nil { + return fmt.Errorf("can't save XD zip file: %w", err) + } + defer os.Remove(saved) + + err = unzip(saved, C.UIPath) + if err != nil { + return fmt.Errorf("can't extract XD zip file: %w", err) + } + + err = os.Rename(path.Join(C.UIPath, "metacubexd-gh-pages"), path.Join(C.UIPath, "xd")) + if err != nil { + return fmt.Errorf("can't rename folder: %w", err) + } + return nil +} + +func unzip(src, dest string) error { + r, err := zip.OpenReader(src) + if err != nil { + return err + } + defer r.Close() + + for _, f := range r.File { + fpath := filepath.Join(dest, f.Name) + + if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) { + return fmt.Errorf("invalid file path: %s", fpath) + } + + if f.FileInfo().IsDir() { + os.MkdirAll(fpath, os.ModePerm) + continue + } + + if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { + return err + } + + outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + + rc, err := f.Open() + if err != nil { + return err + } + + _, err = io.Copy(outFile, rc) + + outFile.Close() + rc.Close() + + if err != nil { + return err + } + } + return nil +} + +func cleanup(root string) error { + return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if path == root { + // skip root itself + return nil + } + if info.IsDir() { + if err := os.RemoveAll(path); err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + } else { + if err := os.Remove(path); err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + } + return nil + }) +} diff --git a/config/utils.go b/config/utils.go index 799082c4..1fa54634 100644 --- a/config/utils.go +++ b/config/utils.go @@ -1,15 +1,37 @@ package config import ( + "context" "fmt" + "io" "net" + "net/http" "net/netip" + "os" "strings" + "time" "github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/common/structure" + clashHttp "github.com/Dreamacro/clash/component/http" ) +func downloadForBytes(url string) ([]byte, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) + defer cancel() + resp, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + return io.ReadAll(resp.Body) +} + +func saveFile(bytes []byte, path string) error { + return os.WriteFile(path, bytes, 0o644) +} + func trimArr(arr []string) (r []string) { for _, e := range arr { r = append(r, strings.Trim(e, " ")) diff --git a/constant/path.go b/constant/path.go index d7477e0e..e595d920 100644 --- a/constant/path.go +++ b/constant/path.go @@ -15,6 +15,7 @@ const Name = "clash" var ( GeositeName = "GeoSite.dat" GeoipName = "GeoIP.dat" + UIPath = "" ) // Path is used to get the configuration path diff --git a/hub/route/server.go b/hub/route/server.go index d2fecd05..3df6196a 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -49,6 +49,7 @@ type Memory struct { func SetUIPath(path string) { uiPath = C.Path.Resolve(path) + C.UIPath = uiPath } func Start(addr string, tlsAddr string, secret string, diff --git a/hub/route/upgrade.go b/hub/route/upgrade.go index 5e75bc8b..ef5dffaf 100644 --- a/hub/route/upgrade.go +++ b/hub/route/upgrade.go @@ -5,6 +5,7 @@ import ( "net/http" "os" + "github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/hub/updater" "github.com/Dreamacro/clash/log" @@ -15,6 +16,7 @@ import ( func upgradeRouter() http.Handler { r := chi.NewRouter() r.Post("/", upgrade) + r.Post("/xd", updateXD) return r } @@ -43,3 +45,18 @@ func upgrade(w http.ResponseWriter, r *http.Request) { go restartExecutable(execPath) } + +func updateXD(w http.ResponseWriter, r *http.Request) { + err := config.UpdateXD() + if err != nil { + log.Warnln("%s", err) + render.Status(r, http.StatusInternalServerError) + render.JSON(w, r, newError(fmt.Sprintf("%s", err))) + return + } + + render.JSON(w, r, render.M{"status": "ok"}) + if f, ok := w.(http.Flusher); ok { + f.Flush() + } +} diff --git a/hub/updater/updater.go b/hub/updater/updater.go index 90b14c27..1a930c03 100644 --- a/hub/updater/updater.go +++ b/hub/updater/updater.go @@ -32,7 +32,7 @@ var ( workDir string // mu protects all fields below. - mu sync.RWMutex + mu sync.Mutex currentExeName string // 当前可执行文件 updateDir string // 更新目录