gocmdDaemon/main.go

239 lines
4.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package gocmdDaemon
import (
"encoding/json"
"fmt"
"net"
"os"
"strings"
"github.com/google/uuid"
)
type CmdDaemon struct {
Name string // app cli command name
SocketPath string // unix socket path
cmds map[string]CmdHandler
}
type CmdRequest struct {
Id string `json:"id"`
Cmd string `json:"cmd"`
Args string `json:"args"`
IsDebug bool `json:"debug"`
}
type CmdResponse struct {
Id string `json:"id"`
Data string `json:"data"`
Error string `json:"error"`
Continue bool `json:"continue"`
}
type CmdConn struct {
net.Conn
Id string
}
func (c *CmdConn) Write(d string) error {
resp := CmdResponse{
Id: c.Id,
Data: d,
Continue: true,
}
return Write(c.Conn, resp)
}
func (c *CmdConn) WriteError(err error, isContinue bool) error {
resp := CmdResponse{
Id: c.Id,
Error: err.Error(),
Continue: isContinue,
}
return Write(c.Conn, resp)
}
func (c *CmdConn) End(d string) error {
resp := CmdResponse{
Id: c.Id,
Data: d,
Continue: false,
}
return Write(c.Conn, resp)
}
type CmdHandler interface {
Handle(conn *CmdConn, req *CmdRequest) error
Description() string
Usage() string
}
// Listen 启动守护进程并监听Unix socket上的连接
// 参数:
//
// c: CmdDaemon 实例指针
//
// 返回:
//
// error: 监听过程中发生的错误(如果有)
func (c *CmdDaemon) Listen() error {
// 删除已存在的 socket 文件(如果存在)
if err := os.Remove(c.SocketPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove existing socket file: %v", err)
}
// 监听 unix socket
listener, err := net.Listen("unix", c.SocketPath)
if err != nil {
return fmt.Errorf("failed to listen on socket: %v", err)
}
defer listener.Close()
// 设置 socket 文件权限
if err := os.Chmod(c.SocketPath, 0777); err != nil {
return fmt.Errorf("failed to set socket file permissions: %v", err)
}
for {
conn, err := listener.Accept()
if err != nil {
return fmt.Errorf("failed to accept connection: %v", err)
}
// 处理每个连接
go func(conn net.Conn) {
defer conn.Close()
req, err := Read[CmdRequest](conn)
if err != nil {
_ = Write(conn, CmdResponse{
Error: "failed to read request: " + err.Error(),
Continue: false,
})
return
}
cmdHandler, ok := c.cmds[req.Cmd]
if !ok {
_ = Write(conn, CmdResponse{
Error: "unknown command: " + req.Cmd + "\n" + c.Usage(),
Continue: false,
})
return
}
// 执行命令处理程序
cmdConn := &CmdConn{Conn: conn, Id: req.Id}
err = cmdHandler.Handle(cmdConn, req)
if err != nil {
_ = cmdConn.End(err.Error() + cmdHandler.Usage())
}
}(conn)
}
}
// Usage 生成命令使用说明
// 参数:
//
// c: CmdDaemon 实例指针
//
// 返回:
//
// string: 命令使用说明字符串
func (c *CmdDaemon) Usage() string {
usage := fmt.Sprintf("Usage: %s [options] <command> [args...]\n\n", c.Name)
usage += "Options:\n"
usage += " -d, --debug Run command in debug mode\n\n"
usage += "Commands:\n"
for cmd, handler := range c.cmds {
usage += fmt.Sprintf(" %-10s %s\n", cmd, handler.Description())
}
return usage
}
func (c *CmdDaemon) RegisterCmd(cmd string, handler CmdHandler) {
c.cmds[cmd] = handler
}
// isDebug 检查给定的命令是否是调试标志
// 参数:
//
// c: CmdDaemon 实例指针
// cmd: 要检查的命令字符串
//
// 返回:
//
// bool: 如果是调试标志返回true否则返回false
func (c *CmdDaemon) isDebug(cmd string) bool {
return cmd == "--debug" || cmd == "-d"
}
// Run 执行客户端命令并通过Unix socket与守护进程通信
// 参数:
//
// c: CmdDaemon 实例指针
//
// 返回:
//
// error: 执行过程中发生的错误(如果有)
func (c *CmdDaemon) Run() error {
// 从命令参数中解析出是否debug 子命令和剩余参数字符串
args := os.Args[1:]
isDebug := c.isDebug(args[0])
var remainingArgs []string
cmd := ""
if isDebug {
if len(args) > 1 {
cmd = args[1]
remainingArgs = args[2:]
} else {
cmd = "help"
isDebug = false
remainingArgs = []string{}
}
} else {
if len(args) > 0 {
cmd = args[0]
remainingArgs = args[1:]
} else {
cmd = "help"
isDebug = false
remainingArgs = []string{}
}
}
cmdReq := CmdRequest{
Args: strings.Join(remainingArgs, " "),
Cmd: cmd,
Id: uuid.New().String(),
IsDebug: isDebug,
}
// dial unix socket
conn, err := net.Dial("unix", c.SocketPath)
if err != nil {
return err
}
defer conn.Close()
// send cmd request
cmdReqJson, err := json.Marshal(cmdReq)
if err != nil {
return err
}
_, err = conn.Write(cmdReqJson)
if err != nil {
return err
}
for {
resp, err := Read[CmdResponse](conn)
if err != nil {
return err
}
if resp.Error != "" {
fmt.Println(resp.Error)
}
if !resp.Continue {
break
}
}
return nil
}