gocmdDaemon/main.go

270 lines
6.3 KiB
Go
Raw 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 (
"fmt"
"net"
"os"
"strings"
"syscall"
"time"
"flag"
"github.com/google/uuid"
)
type CmdDaemon struct {
Name string // app cli command name
PipePath string // named pipe 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
}
// pipeConnection 是一个适配器,将 *os.File 转换为 net.Conn 接口
type pipeConnection struct {
*os.File
}
// 实现 net.Conn 接口所需的方法
func (p *pipeConnection) LocalAddr() net.Addr { return &pipeAddr{p.Name()} }
func (p *pipeConnection) RemoteAddr() net.Addr { return &pipeAddr{p.Name()} }
func (p *pipeConnection) SetDeadline(t time.Time) error { return nil }
func (p *pipeConnection) SetReadDeadline(t time.Time) error { return nil }
func (p *pipeConnection) SetWriteDeadline(t time.Time) error { return nil }
// pipeAddr 实现 net.Addr 接口
type pipeAddr struct {
name string
}
func (a *pipeAddr) Network() string { return "pipe" }
func (a *pipeAddr) String() string { return a.name }
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 启动守护进程并监听命名管道上的连接
// 参数:
//
// c: CmdDaemon 实例指针
//
// 返回:
//
// error: 监听过程中发生的错误(如果有)
func (c *CmdDaemon) Listen() error {
// 删除已存在的命名管道(如果存在)
if err := os.Remove(c.PipePath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove existing pipe: %v", err)
}
// 创建命名管道
if err := syscall.Mkfifo(c.PipePath, 0666); err != nil {
return fmt.Errorf("failed to create named pipe: %v", err)
}
// 设置管道文件权限
if err := os.Chmod(c.PipePath, 0666); err != nil {
return fmt.Errorf("failed to set pipe permissions: %v", err)
}
pipe, err := os.OpenFile(c.PipePath, os.O_RDWR, 0)
if err != nil {
return fmt.Errorf("failed to open pipe for reading: %v", err)
}
defer pipe.Close()
// 将文件转换为io.ReadWriter接口以便与现有的Read/Write函数兼容
pipeConn := &pipeConnection{pipe}
for {
// 打开命名管道进行读取
// 处理请求
// go func(pipe *os.File) {
req, err := Read[CmdRequest](pipeConn)
if err != nil {
_ = Write(pipeConn, CmdResponse{
Error: "failed to read request: " + err.Error(),
Continue: false,
})
break
}
cmdHandler, ok := c.cmds[req.Cmd]
if !ok {
_ = Write(pipeConn, CmdResponse{
Error: "unknown command: " + req.Cmd + "\n" + c.Usage(),
Continue: false,
})
continue
}
// 执行命令处理程序
cmdConn := &CmdConn{Conn: pipeConn, Id: req.Id}
err = cmdHandler.Handle(cmdConn, req)
if err != nil {
_ = cmdConn.End(err.Error() + cmdHandler.Usage())
}
// }(pipe)
}
return nil
}
// 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() (*CmdResponse, error) {
// 定义flag: -debug 和 -daemon
// flag := flag.NewFlagSet(c.Name, flag.ContinueOnError)
var isDebug bool
flag.BoolVar(&isDebug, "debug", false, "Run command in debug mode")
flag.BoolVar(&isDebug, "d", false, "Run command in debug mode")
var daemon bool
flag.BoolVar(&daemon, "daemon", false, "Run command in daemon mode")
flag.BoolVar(&daemon, "D", false, "Run command in daemon mode")
flag.Parse()
// 从命令参数中解析出是否debug 子命令和剩余参数字符串
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,
}
// 打开命名管道进行写入
pipe, err := os.OpenFile(c.PipePath, os.O_RDWR, 0)
if err != nil {
return nil, fmt.Errorf("failed to open pipe for writing: %v", err)
}
defer pipe.Close()
// 将文件转换为io.ReadWriter接口以便与现有的Read/Write函数兼容
pipeConn := &pipeConnection{pipe}
// 发送命令请求
err = Write(pipeConn, cmdReq)
if err != nil {
return nil, fmt.Errorf("failed to write request to pipe: %v", err)
}
for {
resp, err := Read[CmdResponse](pipeConn)
if err != nil {
return nil, fmt.Errorf("failed to read response from pipe: %v", err)
}
if resp.Error != "" {
fmt.Println(resp.Error)
}
if !resp.Continue {
break
}
}
return nil
}