Files
herolib/lib/osal/tmux/tmux_state.v
2025-10-12 12:30:19 +03:00

158 lines
4.5 KiB
V

module tmux
import incubaid.herolib.osal.core as osal
import crypto.md5
import json
import time
import incubaid.herolib.ui.console
// Command state structure for Redis storage
pub struct CommandState {
pub mut:
cmd_md5 string // MD5 hash of the command
cmd_text string // Original command text
status string // running|finished|failed|unknown
pid int // Process ID of the command
started_at string // Timestamp when command started
last_check string // Last time status was checked
pane_id int // Pane ID for reference
}
// Generate Redis key for command state tracking
// Pattern: herotmux:${session}:${window}|${pane}
pub fn (p &Pane) get_state_key() string {
return 'herotmux:${p.window.session.name}:${p.window.name}|${p.id}'
}
// Generate MD5 hash for a command (normalized)
pub fn normalize_and_hash_command(cmd string) string {
// Normalize command: trim whitespace, normalize newlines
normalized := cmd.trim_space().replace('\r\n', '\n').replace('\r', '\n')
return md5.hexhash(normalized)
}
// Store command state in Redis
pub fn (mut p Pane) store_command_state(cmd string, status string, pid int) ! {
key := p.get_state_key()
cmd_hash := normalize_and_hash_command(cmd)
now := time.now().format_ss_milli()
state := CommandState{
cmd_md5: cmd_hash
cmd_text: cmd
status: status
pid: pid
started_at: now
last_check: now
pane_id: p.id
}
state_json := json.encode(state)
p.window.session.tmux.redis.set(key, state_json)!
console.print_debug('Stored command state for pane ${p.id}: ${cmd_hash[..8]}... status=${status}')
}
// Retrieve command state from Redis
pub fn (mut p Pane) get_command_state() ?CommandState {
key := p.get_state_key()
state_json := p.window.session.tmux.redis.get(key) or { return none }
if state_json.len == 0 {
return none
}
state := json.decode(CommandState, state_json) or {
console.print_debug('Failed to decode command state for pane ${p.id}: ${err}')
return none
}
return state
}
// Check if command has changed by comparing MD5 hashes
pub fn (mut p Pane) has_command_changed(new_cmd string) bool {
stored_state := p.get_command_state() or { return true }
new_hash := normalize_and_hash_command(new_cmd)
return stored_state.cmd_md5 != new_hash
}
// Update command status in Redis
pub fn (mut p Pane) update_command_status(status string) ! {
mut stored_state := p.get_command_state() or { return }
stored_state.status = status
stored_state.last_check = time.now().format_ss_milli()
key := p.get_state_key()
state_json := json.encode(stored_state)
p.window.session.tmux.redis.set(key, state_json)!
console.print_debug('Updated command status for pane ${p.id}: ${status}')
}
// Clear command state from Redis (when pane is reset or command is removed)
pub fn (mut p Pane) clear_command_state() ! {
key := p.get_state_key()
p.window.session.tmux.redis.del(key) or {
console.print_debug('Failed to clear command state for pane ${p.id}: ${err}')
}
console.print_debug('Cleared command state for pane ${p.id}')
}
// Check if stored command is currently running by verifying the PID
pub fn (mut p Pane) is_stored_command_running() bool {
stored_state := p.get_command_state() or { return false }
if stored_state.pid <= 0 {
return false
}
// Use osal to check if process exists
return osal.process_exists(stored_state.pid)
}
// Get all command states for a session (useful for debugging/monitoring)
pub fn (mut s Session) get_all_command_states() !map[string]CommandState {
mut states := map[string]CommandState{}
// Get all keys matching the session pattern
pattern := 'herotmux:${s.name}:*'
keys := s.tmux.redis.keys(pattern)!
for key in keys {
state_json := s.tmux.redis.get(key) or { continue }
if state_json.len == 0 {
continue
}
state := json.decode(CommandState, state_json) or {
console.print_debug('Failed to decode state for key ${key}: ${err}')
continue
}
states[key] = state
}
return states
}
// Clean up stale command states (for maintenance)
pub fn (mut s Session) cleanup_stale_command_states() ! {
states := s.get_all_command_states()!
for key, state in states {
// Check if the process is still running
if state.pid > 0 && !osal.process_exists(state.pid) {
// Process is dead, update status
mut updated_state := state
updated_state.status = 'finished'
updated_state.last_check = time.now().format_ss_milli()
state_json := json.encode(updated_state)
s.tmux.redis.set(key, state_json)!
console.print_debug('Updated stale command state ${key}: process ${state.pid} no longer exists')
}
}
}