use super::super::*;
use super::MissionControlApp;
struct SessionMaintenanceResult {
status: String,
rows: Result<Vec<state::SessionPickerRow>, String>,
}
struct SessionMaintenanceWorker {
generation: u64,
pruning: bool,
handle: super::completion_worker::CompletionWorker<SessionMaintenanceResult>,
}
#[derive(Default)]
pub(super) struct SessionMaintenanceRuntime {
worker: Option<SessionMaintenanceWorker>,
}
impl SessionMaintenanceRuntime {
pub(super) fn is_pending(&self) -> bool {
self.worker.is_some()
}
pub(super) fn is_pruning(&self) -> bool {
self.worker.as_ref().is_some_and(|worker| worker.pruning)
}
pub(super) fn cleanup(&mut self) -> Vec<String> {
let Some(worker) = self.worker.take() else {
return Vec::new();
};
let deadline = Instant::now() + WORKER_EXIT_JOIN_TIMEOUT;
while !worker.handle.is_finished() && Instant::now() < deadline {
thread::sleep(Duration::from_millis(5));
}
if !worker.handle.is_finished() {
return vec!["session maintenance did not finish before shutdown".into()];
}
match worker.handle.join() {
Ok(Some(result)) => result.rows.err().into_iter().collect(),
Ok(None) => Vec::new(),
Err(error) => vec![error],
}
}
}
impl MissionControlApp {
pub(super) fn start_session_maintenance(
&mut self,
prune_arg: Option<String>,
visible_rows: Option<usize>,
ui: &mut state::MissionControlState,
) {
if self.session_maintenance.is_pending() {
ui.status = "session maintenance already in progress".into();
return;
}
let pruning = prune_arg.is_some();
if pruning
&& (self.pending_session_switch.is_some() || self.active_run || self.worker.is_some())
{
ui.status = "cannot prune sessions while a prompt or session switch is running".into();
return;
}
let manager = self.state.session_manager.clone();
let active = self.state.active_session_id().map(str::to_owned);
let cwd = self.state.cwd.clone();
let handle = super::completion_worker::CompletionWorker::spawn(
"magi-session-maintenance",
self.events.clone(),
move || {
let status = if let Some(arg) = prune_arg {
execute_prune_sessions_command(&manager, active.as_deref(), Some(&arg))
.map_err(|error| error.to_string())?
} else {
"select session".into()
};
let rows = session_picker_rows(&manager, active.as_deref(), &cwd)
.map_err(|error| format!("failed to list sessions: {error}"));
Ok(SessionMaintenanceResult { status, rows })
},
);
match handle {
Ok(handle) => {
self.session_maintenance.worker = Some(SessionMaintenanceWorker {
generation: self.session_generation,
pruning,
handle,
});
if let Some(visible_rows) = visible_rows {
ui.open_session_picker(Vec::new(), visible_rows);
}
ui.status = if pruning {
"pruning sessions…"
} else {
"loading sessions…"
}
.into();
}
Err(error) => ui.status = format!("failed to start session maintenance: {error}"),
}
}
pub(in crate::tui) fn reap_session_maintenance(
&mut self,
ui: &mut state::MissionControlState,
) -> bool {
let Some(worker) = self.session_maintenance.worker.as_mut() else {
return false;
};
let result = worker.handle.take_result();
let generation = worker.generation;
let pruning = worker.pruning;
if result.is_some() {
worker.pruning = false;
}
if worker.handle.ready_to_reap()
&& let Some(worker) = self.session_maintenance.worker.take()
{
let _ = worker.handle.join();
}
let Some(result) = result else {
return false;
};
if generation != self.session_generation {
return false;
}
if !pruning && ui.modals.session_picker.is_none() {
return false;
}
match result {
Ok(result) => {
ui.status = result.status;
match result.rows {
Ok(rows) => {
if let Some(picker) = &mut ui.modals.session_picker {
picker.selected = picker.selected.min(rows.len().saturating_sub(1));
picker.rows = rows;
ui.set_scroll_offset(&ui.scroll_views.session_list, 0);
ui.set_scroll_offset(&ui.scroll_views.session_preview, 0);
self.request_selected_session_preview(ui);
}
}
Err(error) => ui.status = format!("{}; {error}", ui.status),
}
}
Err(error) => ui.status = error,
}
true
}
}