package configui import ( "bytes" "errors" "fmt" "os" "os/exec" "runtime" "sync" "time" ) type File struct { Path string `json:"path"` Name string `json:"name"` Cmd string `json:"cmd"` // RO is readonly RO bool `json:"ro"` Lang string `json:"lang"` // Order order of the display on ui Order int `json:"order"` // used for parsing post data Data string `json:"data,omitempty"` lock sync.RWMutex `json:"-"` owner *ConfigUI `json:"-"` run chan struct{} `json:"-"` pid int `json:"-"` // deprecated Action string `json:"action,omitempty"` } func (f *File) Read() ([]byte, error) { f.lock.RLock() defer f.lock.RUnlock() data, err := os.ReadFile(f.Path) if err != nil { f.owner.log.Error(err) return nil, err } return data, nil } func (f *File) Write(data []byte) error { if f.RO { return errors.New("this file has readonly set") } f.lock.Lock() defer f.lock.Unlock() info, err := os.Stat(f.Path) if err != nil { return err } return os.WriteFile(f.Path, data, info.Mode()) } func (f *File) Do(CmdTimeout time.Duration) (string, error) { if f.Cmd == "" { return "", nil } f.lock.RLock() defer f.lock.RUnlock() // limit running instance to 1 if cap(f.run) == 0 { f.run = make(chan struct{}, 1) } select { case f.run <- struct{}{}: defer func() { <-f.run }() default: f.owner.log.Error("task is running: ", f.Name) return "", fmt.Errorf("another task of %s is running with pid: %d", f.Name, f.pid) } // prepare cmd cmd := &exec.Cmd{} if runtime.GOOS == "windows" { cmd = exec.Command(WIN_SHELL, "/c", f.Cmd) } else { cmd = exec.Command(UNIX_SHELL, "-c", f.Cmd) } f.owner.log.Info("DO: ", f.Cmd) done := make(chan string, 1) var b bytes.Buffer cmd.Stdout = &b cmd.Stderr = &b err := cmd.Start() if err != nil { f.owner.log.Error("cmd start error: ", err) panic(err) } go func() { f.pid = cmd.Process.Pid cmd.Wait() done <- b.String() }() select { case <-time.After(CmdTimeout): cmd.Process.Kill() f.owner.log.Error("timeout") return "", errors.New("command timeout") case out := <-done: f.owner.log.Info("\n", out) return out, nil } }