159 lines
3.4 KiB
Go
159 lines
3.4 KiB
Go
package session
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"time"
|
|
|
|
"supervisor/internal/domain"
|
|
"supervisor/internal/store"
|
|
"supervisor/internal/util"
|
|
)
|
|
|
|
var ErrSessionNotFound = errors.New("session not found")
|
|
var ErrSessionRunning = errors.New("session is running")
|
|
|
|
type Manager struct {
|
|
mu sync.RWMutex
|
|
store store.SessionStore
|
|
factory PTYFactory
|
|
runtimes map[string]*Session
|
|
scrollbackLimit int
|
|
}
|
|
|
|
func NewManager(store store.SessionStore, factory PTYFactory) *Manager {
|
|
if factory == nil {
|
|
factory = DefaultPTYFactory{}
|
|
}
|
|
return &Manager{
|
|
store: store,
|
|
factory: factory,
|
|
runtimes: make(map[string]*Session),
|
|
scrollbackLimit: 512 * 1024,
|
|
}
|
|
}
|
|
|
|
func (m *Manager) CreateSession(ctx context.Context, params CreateSessionParams) (domain.Session, error) {
|
|
command := params.Command
|
|
if command == "" {
|
|
command = "bash"
|
|
}
|
|
s := domain.Session{
|
|
ID: util.NewID("sess"),
|
|
Name: params.Name,
|
|
AgentID: params.AgentID,
|
|
Command: command,
|
|
Status: domain.SessionStatusCreated,
|
|
CreatedAt: time.Now().UTC(),
|
|
}
|
|
if s.Name == "" {
|
|
s.Name = s.ID
|
|
}
|
|
|
|
runtime := NewSession(s, m.scrollbackLimit, func(session domain.Session) {
|
|
_ = m.store.Upsert(context.Background(), session)
|
|
})
|
|
|
|
m.mu.Lock()
|
|
m.runtimes[s.ID] = runtime
|
|
m.mu.Unlock()
|
|
|
|
if err := m.store.Upsert(ctx, s); err != nil {
|
|
return domain.Session{}, err
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
func (m *Manager) StartSession(_ context.Context, id string) error {
|
|
runtime, err := m.runtimeByID(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return runtime.Start(m.factory)
|
|
}
|
|
|
|
func (m *Manager) StopSession(_ context.Context, id string) error {
|
|
runtime, err := m.runtimeByID(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return runtime.Stop()
|
|
}
|
|
|
|
func (m *Manager) ListSessions(ctx context.Context) ([]domain.Session, error) {
|
|
return m.store.List(ctx)
|
|
}
|
|
|
|
func (m *Manager) GetSession(ctx context.Context, id string) (domain.Session, error) {
|
|
s, ok, err := m.store.Get(ctx, id)
|
|
if err != nil {
|
|
return domain.Session{}, err
|
|
}
|
|
if !ok {
|
|
return domain.Session{}, ErrSessionNotFound
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
func (m *Manager) WriteInput(_ context.Context, id string, input string) error {
|
|
runtime, err := m.runtimeByID(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return runtime.WriteInput(input)
|
|
}
|
|
|
|
func (m *Manager) Subscribe(id string) (<-chan domain.Event, func(), error) {
|
|
runtime, err := m.runtimeByID(id)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
_, ch, cancel := runtime.Subscribe()
|
|
return ch, cancel, nil
|
|
}
|
|
|
|
func (m *Manager) Resize(_ context.Context, id string, cols, rows int) error {
|
|
runtime, err := m.runtimeByID(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return runtime.Resize(cols, rows)
|
|
}
|
|
|
|
func (m *Manager) Scrollback(id string) ([]byte, error) {
|
|
runtime, err := m.runtimeByID(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return runtime.Scrollback(), nil
|
|
}
|
|
|
|
func (m *Manager) DeleteSession(ctx context.Context, id string) error {
|
|
runtime, err := m.runtimeByID(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
snapshot := runtime.Snapshot()
|
|
if snapshot.Status == domain.SessionStatusRunning {
|
|
return ErrSessionRunning
|
|
}
|
|
|
|
m.mu.Lock()
|
|
delete(m.runtimes, id)
|
|
m.mu.Unlock()
|
|
|
|
return m.store.Delete(ctx, id)
|
|
}
|
|
|
|
func (m *Manager) runtimeByID(id string) (*Session, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
runtime, ok := m.runtimes[id]
|
|
if !ok {
|
|
return nil, ErrSessionNotFound
|
|
}
|
|
return runtime, nil
|
|
}
|