termtap/internal/app/session.go
Hayden Hargreaves 453a26e529 feat: events pane complete and process restart
Lots of colors and new process restart feature.
2026-04-18 15:10:23 -07:00

145 lines
2.5 KiB
Go

package app
import (
"errors"
"fmt"
"sync"
"syscall"
"time"
"termtap.dev/internal/model"
"termtap.dev/internal/process"
"termtap.dev/internal/proxy"
)
type Session struct {
Events <-chan model.Event
ch chan model.Event
proxy *model.ProxyServer
proc *model.Process
cmd model.Command
addr string
once sync.Once
restartMu sync.Mutex
restarting bool
stopped bool
}
var ErrRestartInProgress = errors.New("restart already in progress")
var ErrSessionStopped = errors.New("session is stopped")
func StartSession(cmd model.Command, addr string) (*Session, error) {
msgs := make(chan model.Event, 256)
ps, err := proxy.NewProxyServer(addr, msgs)
if err != nil {
return nil, err
}
go StartProxy(ps, msgs)
proc, err := StartProcess(cmd, addr, msgs)
if err != nil {
proxy.Destroy(ps, msgs)
return nil, err
}
return &Session{
Events: msgs,
ch: msgs,
proxy: ps,
proc: proc,
cmd: cmd,
addr: addr,
}, nil
}
func (s *Session) Stop() {
if s == nil {
return
}
s.once.Do(func() {
s.restartMu.Lock()
s.stopped = true
proc := s.proc
proxyServer := s.proxy
ch := s.ch
s.restartMu.Unlock()
StopProcess(proc, ch, syscall.SIGTERM)
proxy.Destroy(proxyServer, ch)
})
}
func (s *Session) RestartProcess() error {
if s == nil {
return fmt.Errorf("session is nil")
}
s.restartMu.Lock()
if s.stopped {
s.restartMu.Unlock()
return ErrSessionStopped
}
if s.restarting {
s.restartMu.Unlock()
return ErrRestartInProgress
}
s.restarting = true
current := s.proc
cmd := s.cmd
addr := s.addr
ch := s.ch
s.restartMu.Unlock()
defer func() {
s.restartMu.Lock()
s.restarting = false
s.restartMu.Unlock()
}()
ch <- model.Event{
Time: time.Now().Local(),
Type: model.EventTypeProcessRestarting,
Body: fmt.Sprintf("restarting process '%s'", process.CommandString(cmd)),
}
if current != nil {
StopProcess(current, ch, syscall.SIGTERM)
if !waitForProcessStop(current, 3*time.Second) {
return fmt.Errorf("timeout while waiting for process to stop")
}
}
proc, err := StartProcess(cmd, addr, ch)
if err != nil {
return err
}
s.restartMu.Lock()
defer s.restartMu.Unlock()
if s.stopped {
StopProcess(proc, ch, syscall.SIGTERM)
return fmt.Errorf("session stopped during restart: %w", ErrSessionStopped)
}
s.proc = proc
return nil
}
func waitForProcessStop(proc *model.Process, timeout time.Duration) bool {
if proc == nil || proc.Done == nil {
return true
}
select {
case <-proc.Done:
return true
case <-time.After(timeout):
return false
}
}