Skip to content

Commit

Permalink
feat: 添加检测更新功能
Browse files Browse the repository at this point in the history
refactor: 调整程序结构,拆分代码
  • Loading branch information
junlongzzz committed Nov 15, 2024
1 parent d4b0ccb commit d5f752e
Show file tree
Hide file tree
Showing 2 changed files with 268 additions and 225 deletions.
291 changes: 66 additions & 225 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,253 +1,31 @@
package main

import (
"embed"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"runtime/debug"
"strconv"
"strings"
"time"

"github.com/energye/systray"
"golang.org/x/sys/windows"
)

const (
// AppName 程序名称
AppName = "Gohomo"
// CoreShowName 核心名称
CoreShowName = "Mihomo"
// AppGitHubRepo 程序GitHub仓库
AppGitHubRepo = "https://github.com/junlongzzz/gohomo"
)

var (
build string // 编译时的git提交哈希
workDir string // 工作目录
logDir string // 日志目录

//go:embed static/*
staticFiles embed.FS // 嵌入静态文件
)

// 发生错误退出程序时的提示,避免无法看到错误消息
func fatal(v ...any) {
log.Println(v...)
messageBoxAlert(AppName, fmt.Sprintln(v...))
// 退出程序
os.Exit(0)
}

// 删除清理指定过期时长的日志文件
func delOutdatedLogs(age time.Duration) {
_ = filepath.WalkDir(logDir, func(path string, info os.DirEntry, err error) error {
if err != nil {
return err
}
if info.IsDir() && path != logDir {
// 跳过子目录
return filepath.SkipDir
}
// 判断文件名是否以.log结尾
if !strings.HasSuffix(info.Name(), ".log") {
return nil
}
// 获取文件创建时间
fileInfo, err := info.Info()
if err != nil {
return err
}
fileAge := time.Now().Sub(fileInfo.ModTime())
if fileAge > age {
// 删除过期文件
_ = os.Remove(path)
}
return nil
})
}

// 获取pid文件路径
func getPidFilePath() string {
// 临时目录内
return filepath.Join(os.TempDir(), "gohomo.pid")
}

// 检查是否为单实例
func checkSingleInstance() {
pidFilePath := getPidFilePath()
if isFileExist(pidFilePath) {
bytes, _ := os.ReadFile(pidFilePath)
if bytes != nil && len(bytes) > 0 {
// 判断pid对应进程是否还在运行
pid, err := strconv.Atoi(string(bytes))
if err == nil && pid > 0 && isProcessRunningByPid(pid) {
fatal("Another instance of Gohomo is running.")
}
}
}

// 保存当前进程的pid到文件
err := os.WriteFile(pidFilePath, []byte(strconv.Itoa(os.Getpid())), 0644)
if err != nil {
fatal("Failed to write pid file:", err)
}
}

func onReady() {
bytes, err := staticFiles.ReadFile("static/icon.ico")
if err == nil {
systray.SetIcon(bytes)
}
systray.SetTitle(AppName)
systray.SetTooltip(AppName)

systray.AddMenuItem(fmt.Sprintf("%s %s", AppName, build), AppName).Click(func() {
// 点击打开主页
_ = openBrowser("https://github.com/junlongzzz/gohomo")
})

// 分割线
systray.AddSeparator()

coreItem := systray.AddMenuItem(CoreShowName, CoreShowName)
coreItem.Click(func() {
// 点击打开主页
_ = openBrowser("https://github.com/MetaCubeX/mihomo")
})

sysProxyItem := systray.AddMenuItemCheckbox("System Proxy", "Set or Unset", getProxyEnable())
sysProxyItem.Click(func() {
if sysProxyItem.Checked() {
if unsetProxy() {
sysProxyItem.Uncheck()
}
} else {
if setCoreProxy() {
sysProxyItem.Check()
}
}
})

restartCoreItem := systray.AddMenuItem("Restart Core", "Restart Core")
restartCoreItem.Click(func() {
// 重新加载核心配置
if err := loadCoreConfig(); err != nil {
messageBoxAlert(AppName, fmt.Sprint(err))
return
}
if restartCore() {
if sysProxyItem != nil && sysProxyItem.Checked() {
// 重新设置代理
setCoreProxy()
}
} else {
messageBoxAlert(AppName, "Failed to restart core")
}
})

systray.AddMenuItem("Edit Config", "Edit Config").Click(func() {
// 打开配置文件
_ = openBrowser(coreConfigPath)
})

dashboardItem := systray.AddMenuItem("Core Dashboard", "Core Dashboard")
dashboardItem.AddSubMenuItem("External UI", "External UI").Click(func() {
_ = openBrowser(coreConfig.ExternalUiAddr)
})
dashboardItem.AddSubMenuItem("Official UI", "Official UI").Click(func() {
_ = openBrowser(coreConfig.OfficialUiAddr)
})
dashboardItem.AddSubMenuItem("YACD UI", "YACD UI").Click(func() {
_ = openBrowser(coreConfig.YACDUiAddr)
})

// 分割线
systray.AddSeparator()

// 打开本地工作目录
systray.AddMenuItem("Open Work Directory", "Open Work Directory").Click(func() {
_ = openDirectory(workDir)
})

var openShellFn = func(shell string) {
cmd := exec.Command(shell)
cmd.Dir = workDir
// 设置代理环境变量
cmd.Env = append(os.Environ(),
fmt.Sprintf("HTTP_PROXY=http://127.0.0.1:%d", coreConfig.HttpProxyPort),
fmt.Sprintf("HTTPS_PROXY=http://127.0.0.1:%d", coreConfig.HttpProxyPort))
cmd.SysProcAttr = &windows.SysProcAttr{
CreationFlags: windows.CREATE_NEW_CONSOLE | windows.CREATE_UNICODE_ENVIRONMENT | windows.CREATE_NEW_PROCESS_GROUP,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
messageBoxAlert(AppName, fmt.Sprintf("Failed to start %s: %v", shell, err))
}
}
// 打开powershell
systray.AddMenuItem("Open PowerShell", "Open PowerShell").Click(func() {
ps := "pwsh.exe"
// 先判断 pwsh.exe 是否在环境变量内存在
if _, err := exec.LookPath(ps); err != nil {
// 不存在使用系统默认的 PowerShell
ps = "powershell.exe"
}
openShellFn(ps)
})
// 打开命令行
systray.AddMenuItem("Open Command Prompt", "Open Command Prompt").Click(func() {
openShellFn("cmd.exe")
})

// 分割线
systray.AddSeparator()

systray.AddMenuItem("About", "About").Click(func() {
about := fmt.Sprintf(`Name: %s
Description: %s
Build Hash: %s
Go Version: %s
---
Work Directory: %s
Log Directory: %s
Core Directory: %s
Core Path: %s
Core Version: %s
Config Path: %s`,
AppName, "Wrapper for Mihomo written in Golang.", build, runtime.Version(), workDir, logDir, coreDir, corePath, getCoreVersion(), coreConfigPath)
messageBoxAlert(AppName, about)
})

exitItem := systray.AddMenuItem("Exit", "Exit")
exitItem.Click(func() { systray.Quit() })

// 托盘点击事件处理函数
var clickFn = func(menu systray.IMenu) {
if menu != nil {
coreItem.SetTitle(fmt.Sprintf("%s %s", CoreShowName, getCoreVersion()))
_ = menu.ShowMenu()
}
}
// 左键点击托盘时显示菜单
systray.SetOnClick(clickFn)
// 右键点击托盘
systray.SetOnRClick(clickFn)
}

func onExit() {
// 退出程序后的处理操作
// 清理pid文件,写入-1
_ = os.WriteFile(getPidFilePath(), []byte("-1"), 0644)
unsetProxy()
stopCore()
os.Exit(0)
}

func main() {
// 设置高DPI感知,避免界面模糊
setDPIAware()
Expand Down Expand Up @@ -341,5 +119,68 @@ func main() {
}

// 系统托盘
systray.Run(onReady, onExit)
initSystray()
}

// 发生错误退出程序时的提示,避免无法看到错误消息
func fatal(v ...any) {
log.Println(v...)
messageBoxAlert(AppName, fmt.Sprintln(v...))
// 退出程序
os.Exit(0)
}

// 获取pid文件路径
func getPidFilePath() string {
// 临时目录内
return filepath.Join(os.TempDir(), "gohomo.pid")
}

// 检查是否为单实例
func checkSingleInstance() {
pidFilePath := getPidFilePath()
if isFileExist(pidFilePath) {
bytes, _ := os.ReadFile(pidFilePath)
if bytes != nil && len(bytes) > 0 {
// 判断pid对应进程是否还在运行
pid, err := strconv.Atoi(string(bytes))
if err == nil && pid > 0 && isProcessRunningByPid(pid) {
fatal("Another instance of Gohomo is running.")
}
}
}

// 保存当前进程的pid到文件
err := os.WriteFile(pidFilePath, []byte(strconv.Itoa(os.Getpid())), 0644)
if err != nil {
fatal("Failed to write pid file:", err)
}
}

// 删除清理指定过期时长的日志文件
func delOutdatedLogs(age time.Duration) {
_ = filepath.WalkDir(logDir, func(path string, info os.DirEntry, err error) error {
if err != nil {
return err
}
if info.IsDir() && path != logDir {
// 跳过子目录
return filepath.SkipDir
}
// 判断文件名是否以.log结尾
if !strings.HasSuffix(info.Name(), ".log") {
return nil
}
// 获取文件创建时间
fileInfo, err := info.Info()
if err != nil {
return err
}
fileAge := time.Now().Sub(fileInfo.ModTime())
if fileAge > age {
// 删除过期文件
_ = os.Remove(path)
}
return nil
})
}
Loading

0 comments on commit d5f752e

Please sign in to comment.