Files
SocksRevive-PC/internal/engine/process.go
2026-05-16 00:18:06 -03:00

92 lines
1.8 KiB
Go

package engine
import (
"bufio"
"context"
"errors"
"io"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
"socksrevivepc/internal/oscmd"
)
type ManagedProcess struct {
cmd *exec.Cmd
name string
logger *Logger
mu sync.Mutex
done chan error
}
func StartProcess(ctx context.Context, root, name, exe string, args []string, logger *Logger) (*ManagedProcess, error) {
if strings.TrimSpace(exe) == "" {
return nil, errors.New(name + " executable path is empty")
}
if !filepath.IsAbs(exe) {
exe = filepath.Join(root, exe)
}
cmd := oscmd.CommandContext(ctx, exe, args...)
cmd.Dir = root
stdout, _ := cmd.StdoutPipe()
stderr, _ := cmd.StderrPipe()
p := &ManagedProcess{cmd: cmd, name: name, logger: logger, done: make(chan error, 1)}
if err := cmd.Start(); err != nil {
return nil, err
}
logger.Add("info", "%s started: %s %s", name, exe, strings.Join(args, " "))
go p.pipe(stdout, "info")
go p.pipe(stderr, "warn")
go func() {
err := cmd.Wait()
p.done <- err
if err != nil {
logger.Add("warn", "%s stopped: %v", name, err)
} else {
logger.Add("info", "%s stopped", name)
}
}()
return p, nil
}
func (p *ManagedProcess) Exited() (bool, error) {
if p == nil || p.done == nil {
return true, nil
}
select {
case err := <-p.done:
return true, err
default:
return false, nil
}
}
func (p *ManagedProcess) pipe(r io.Reader, level string) {
s := bufio.NewScanner(r)
for s.Scan() {
line := strings.TrimSpace(s.Text())
if line != "" {
p.logger.Add(level, "%s: %s", p.name, line)
}
}
}
func (p *ManagedProcess) Stop() {
p.mu.Lock()
defer p.mu.Unlock()
if p == nil || p.cmd == nil || p.cmd.Process == nil {
return
}
if runtime.GOOS == "windows" {
_ = p.cmd.Process.Kill()
} else {
_ = p.cmd.Process.Signal(ioSignalInterrupt())
time.Sleep(500 * time.Millisecond)
_ = p.cmd.Process.Kill()
}
}