package writeserver import ( "fmt" "net" "os/exec" "path/filepath" "sync" "time" ) var ( mu sync.Mutex writeProc *exec.Cmd writeReady bool hugoProc *exec.Cmd WritePort int WriteDir string ) func Init(writePort int, writeDir string) { WritePort = writePort WriteDir = writeDir } func StartWriteServer() map[string]interface{} { mu.Lock() defer mu.Unlock() if writeProc != nil { if writeReady { return map[string]interface{}{"started": false, "reason": "already_running"} } return map[string]interface{}{"started": false, "reason": "starting"} } writeReady = false writeProc = exec.Command(filepath.Join(WriteDir, "writeserver.exe")) writeProc.Dir = WriteDir if err := writeProc.Start(); err != nil { writeProc = nil return map[string]interface{}{"started": false, "reason": err.Error()} } writeReady = true go func() { err := writeProc.Wait() mu.Lock() defer mu.Unlock() fmt.Printf("[write] exited: %v\n", err) writeProc = nil writeReady = false }() return map[string]interface{}{"started": true} } func StopWriteServer() bool { mu.Lock() defer mu.Unlock() if hugoProc != nil { killProcessTree(hugoProc) hugoProc = nil } if writeProc == nil { return false } killProcessTree(writeProc) writeProc = nil writeReady = false return true } func killProcessTree(cmd *exec.Cmd) { if cmd == nil || cmd.Process == nil { return } // On Windows, use taskkill /T to kill process tree pid := cmd.Process.Pid killCmd := exec.Command("taskkill", "/PID", fmt.Sprintf("%d", pid), "/T", "/F") killCmd.Run() cmd.Process.Kill() } func StartHugoIfNeeded() { mu.Lock() defer mu.Unlock() if hugoProc != nil { return } hugoDir := WriteDir + "/.." hugoProc = exec.Command("hugo", "server", "--bind", "0.0.0.0", "--port", "1313", "--disableFastRender") hugoProc.Dir = hugoDir if err := hugoProc.Start(); err != nil { fmt.Printf("[hugo] start error: %v\n", err) hugoProc = nil return } fmt.Println("[hugo] started on 0.0.0.0:1313") go func() { err := hugoProc.Wait() mu.Lock() defer mu.Unlock() fmt.Printf("[hugo] exited: %v\n", err) hugoProc = nil }() } func GetWriteStatus() map[string]interface{} { mu.Lock() running := writeProc != nil && writeReady starting := writeProc != nil && !writeReady mu.Unlock() // Also check via TCP if we are not tracking a process if !running && !starting { conn, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", WritePort), 2*time.Second) if err == nil { conn.Close() running = true } } return map[string]interface{}{ "running": running, "starting": starting, } } func SetWriteReady(ready bool) { mu.Lock() defer mu.Unlock() writeReady = ready } func IsWriteReady() bool { mu.Lock() defer mu.Unlock() return writeReady } func IsWriteRunning() bool { mu.Lock() defer mu.Unlock() return writeProc != nil }