use super::*;
use std::cell::OnceCell;
mod backups;
mod bundles;
mod configs;
mod desktop;
mod doctor;
mod drift;
mod hardware;
mod history;
mod home;
mod migration;
mod performance;
mod profiles;
mod recovery;
mod reliability;
mod reports;
mod repos;
mod restic;
mod services;
mod settings;
mod storage;
mod updates;
mod wizard;
#[cfg(test)]
pub(in crate::tui::app) use self::{
bundles::{BUNDLE_ROWS, BundleRow},
configs::ConfigRow,
desktop::DesktopRow,
drift::DriftTarget,
hardware::{HardwareAct, HardwareRow},
home::HomeRow,
migration::MigrationRow,
performance::PerformanceRow,
reliability::Coverage,
repos::{RepoRow, task_shell},
restic::{ResticReading, ResticRow},
services::ServiceRow,
storage::StorageRow,
};
pub use configs::EditRequest;
#[derive(Default)]
pub(in crate::tui::app) struct Screens {
pub(in crate::tui::app) history: history::History,
pub(in crate::tui::app) backups: backups::Backups,
pub(in crate::tui::app) doctor: doctor::Doctor,
pub(in crate::tui::app) updates: updates::Updates,
pub(in crate::tui::app) drift: drift::Drift,
pub(in crate::tui::app) reliability: reliability::Reliability,
pub(in crate::tui::app) hardware: hardware::Hardware,
pub(in crate::tui::app) desktop: desktop::Desktop,
pub(in crate::tui::app) bundles: bundles::Bundles,
pub(in crate::tui::app) services: services::Services,
pub(in crate::tui::app) storage: storage::Storage,
pub(in crate::tui::app) performance: performance::Performance,
pub(in crate::tui::app) recovery: recovery::Recovery,
pub(in crate::tui::app) migration: migration::Migration,
pub(in crate::tui::app) restic: restic::Restic,
pub(in crate::tui::app) repos: repos::Repos,
pub(in crate::tui::app) configs: configs::Configs,
pub(in crate::tui::app) home: home::Home,
}
impl Screens {
pub(in crate::tui::app) fn tool(&self, screen: Screen) -> Option<&dyn ToolScreen> {
Some(match screen {
Screen::History => &self.history,
Screen::Backups => &self.backups,
Screen::Doctor => &self.doctor,
Screen::Updates => &self.updates,
Screen::Drift => &self.drift,
Screen::Reliability => &self.reliability,
Screen::Hardware => &self.hardware,
Screen::Desktop => &self.desktop,
Screen::Bundles => &self.bundles,
Screen::Services => &self.services,
Screen::Storage => &self.storage,
Screen::Performance => &self.performance,
Screen::Recovery => &self.recovery,
Screen::Migration => &self.migration,
Screen::Restic => &self.restic,
Screen::Repositories => &self.repos,
Screen::Configs => &self.configs,
Screen::HomeManager => &self.home,
Screen::Dashboard
| Screen::Area(_)
| Screen::Plan
| Screen::Favorites
| Screen::Settings => return None,
})
}
pub(in crate::tui::app) fn tool_mut(&mut self, screen: Screen) -> Option<&mut dyn ToolScreen> {
Some(match screen {
Screen::History => &mut self.history,
Screen::Backups => &mut self.backups,
Screen::Doctor => &mut self.doctor,
Screen::Updates => &mut self.updates,
Screen::Drift => &mut self.drift,
Screen::Reliability => &mut self.reliability,
Screen::Hardware => &mut self.hardware,
Screen::Desktop => &mut self.desktop,
Screen::Bundles => &mut self.bundles,
Screen::Services => &mut self.services,
Screen::Storage => &mut self.storage,
Screen::Performance => &mut self.performance,
Screen::Recovery => &mut self.recovery,
Screen::Migration => &mut self.migration,
Screen::Restic => &mut self.restic,
Screen::Repositories => &mut self.repos,
Screen::Configs => &mut self.configs,
Screen::HomeManager => &mut self.home,
Screen::Dashboard
| Screen::Area(_)
| Screen::Plan
| Screen::Favorites
| Screen::Settings => return None,
})
}
fn each(&mut self) -> [(Screen, &mut dyn ToolScreen); 18] {
[
(Screen::History, &mut self.history),
(Screen::Backups, &mut self.backups),
(Screen::Doctor, &mut self.doctor),
(Screen::Updates, &mut self.updates),
(Screen::Drift, &mut self.drift),
(Screen::Reliability, &mut self.reliability),
(Screen::Hardware, &mut self.hardware),
(Screen::Desktop, &mut self.desktop),
(Screen::Bundles, &mut self.bundles),
(Screen::Services, &mut self.services),
(Screen::Storage, &mut self.storage),
(Screen::Performance, &mut self.performance),
(Screen::Recovery, &mut self.recovery),
(Screen::Migration, &mut self.migration),
(Screen::Restic, &mut self.restic),
(Screen::Repositories, &mut self.repos),
(Screen::Configs, &mut self.configs),
(Screen::HomeManager, &mut self.home),
]
}
pub(in crate::tui::app) fn after_run(&mut self, cx: &mut Cx, reading: Reading) {
for (screen, tool) in self.each() {
if screen == cx.screen {
tool.after_run(cx, reading);
} else {
tool.stale(reading);
}
}
}
}
impl App {
pub(in crate::tui::app) fn with_screens<R>(
&mut self,
act: impl FnOnce(&mut Screens, &mut Cx) -> R,
) -> R {
let mut asks = Vec::new();
let result = {
let App {
screens,
host,
platform,
catalog,
statuses,
inventory,
queue,
screen,
main_state,
popup,
settings_path,
evidence,
..
} = self;
let mut cx = Cx {
host,
platform,
catalog,
statuses,
inventory: inventory.as_ref(),
queue,
screen: *screen,
selected: main_state,
popup,
settings_path: settings_path.as_deref(),
evidence,
asks: &mut asks,
};
act(screens, &mut cx)
};
for ask in asks {
self.ask(ask);
}
result
}
pub(in crate::tui::app) fn with_tool<R>(
&mut self,
act: impl FnOnce(&mut dyn ToolScreen, &mut Cx) -> R,
) -> Option<R> {
let screen = self.screen;
self.with_screens(|screens, cx| screens.tool_mut(screen).map(|tool| act(tool, cx)))
}
pub fn take_edit(&mut self) -> Option<EditRequest> {
self.screens.configs.take_edit()
}
pub fn edited(&mut self, request: EditRequest, closed: Result<(), String>) {
self.with_screens(|screens, cx| screens.configs.edited(cx, request, closed));
}
pub(in crate::tui::app) fn view(&self) -> View<'_> {
View {
host: &self.host,
platform: &self.platform,
catalog: &self.catalog,
statuses: &self.statuses,
queue: &self.queue,
favorites: &self.favorites,
focused: self.focus == Focus::Main,
theme: self.theme,
icons: self.icons,
hits: &self.hits,
settings_path: self.settings_path.as_deref(),
snapshot_tool: self.snapshot_tool,
snapshots: self.snapshots,
evidence: &self.evidence,
}
}
fn ask(&mut self, ask: Ask) {
match ask {
Ask::Plan(planned) => self.confirm_plan(planned),
Ask::Run(entries) => self.confirm(entries),
Ask::Update(entries) => self.update(entries),
Ask::Effects(planned) => self.show_effects(&planned, false),
Ask::Open(screen) => {
self.open_screen(screen);
self.focus = Focus::Main;
}
Ask::ShowUnit(name) => {
self.screens.services.show(&name);
self.open_screen(Screen::Services);
}
Ask::Inspect(id) => self.inspect(&id),
Ask::ReportWorked(record, only) => self.report_worked(&record, only.as_deref()),
Ask::ShowCheck(wanted) => {
self.with_screens(|screens, cx| screens.doctor.show_check(cx, wanted))
}
Ask::Describe => self.describe(),
Ask::Preview => self.preview(),
Ask::Pin => self.toggle_favorite(),
Ask::CheckStatuses => self.refresh_statuses(),
Ask::Sidebar => self.focus = Focus::Sidebar,
Ask::Clamp => self.clamp_main(),
}
}
}
pub(in crate::tui::app) trait ToolScreen {
fn open(&mut self, cx: &mut Cx);
fn len(&self, view: &View) -> usize;
fn key(&mut self, cx: &mut Cx, code: KeyCode);
fn hints(&self, view: &View, at: Option<usize>, list: &mut Vec<Hint>);
fn draw(&self, view: &View, frame: &mut Frame, area: Rect, state: &mut ListState);
fn poll(&mut self, _cx: &mut Cx) -> bool {
false
}
fn after_run(&mut self, _cx: &mut Cx, _reading: Reading) {}
fn stale(&mut self, _reading: Reading) {}
fn entry<'a>(&self, _view: &View<'a>, _at: Option<usize>) -> Option<&'a Entry> {
None
}
}
pub(in crate::tui::app) enum Ask {
Plan(Result<Plan, EngineError>),
Run(Vec<Entry>),
Update(Vec<Entry>),
Effects(Plan),
Open(Screen),
ShowUnit(String),
Inspect(String),
ReportWorked(helix_toolbox_engine::history::Record, Option<String>),
ShowCheck(&'static str),
Describe,
Preview,
Pin,
CheckStatuses,
Sidebar,
Clamp,
}
pub(in crate::tui::app) struct Cx<'a> {
pub(in crate::tui::app) host: &'a Host,
pub(in crate::tui::app) platform: &'a LinuxInfo,
pub(in crate::tui::app) catalog: &'a Catalog,
pub(in crate::tui::app) statuses: &'a HashMap<String, Status>,
pub(in crate::tui::app) inventory: Option<&'a Inventory>,
pub(in crate::tui::app) queue: &'a mut Vec<Entry>,
pub(in crate::tui::app) screen: Screen,
pub(in crate::tui::app) selected: &'a mut ListState,
pub(in crate::tui::app) popup: &'a mut Option<Popup>,
pub(in crate::tui::app) settings_path: Option<&'a Path>,
evidence: &'a OnceCell<helix_toolbox_engine::reliability::Evidence>,
asks: &'a mut Vec<Ask>,
}
impl Cx<'_> {
pub(in crate::tui::app) fn ask(&mut self, ask: Ask) {
self.asks.push(ask);
}
pub(in crate::tui::app) fn confirm(&mut self, planned: Plan) {
self.ask(Ask::Plan(Ok(planned)));
}
pub(in crate::tui::app) fn confirm_or_say(&mut self, planned: Result<Plan, String>) {
match planned {
Ok(planned) => self.confirm(planned),
Err(why) => self.say("Cannot plan", why),
}
}
pub(in crate::tui::app) fn say(&mut self, title: impl Into<String>, text: impl Into<String>) {
*self.popup = Some(Popup::Text(TextPopup::new(title, text, true)));
}
pub(in crate::tui::app) fn at(&self) -> Option<usize> {
self.selected.selected()
}
pub(in crate::tui::app) fn clamp(&mut self) {
self.ask(Ask::Clamp);
}
pub(in crate::tui::app) fn toggle_queued(&mut self, entry: Entry) {
toggle_queued(self.queue, self.popup, entry);
}
pub(in crate::tui::app) fn evidence(&self) -> helix_toolbox_engine::reliability::Evidence {
evidence(self.evidence, self.catalog)
}
}
pub(in crate::tui::app) struct View<'a> {
pub(in crate::tui::app) host: &'a Host,
pub(in crate::tui::app) platform: &'a LinuxInfo,
pub(in crate::tui::app) catalog: &'a Catalog,
pub(in crate::tui::app) statuses: &'a HashMap<String, Status>,
pub(in crate::tui::app) queue: &'a [Entry],
pub(in crate::tui::app) favorites: &'a [String],
pub(in crate::tui::app) focused: bool,
pub(in crate::tui::app) theme: Theme,
pub(in crate::tui::app) icons: bool,
pub(in crate::tui::app) hits: &'a RefCell<Hits>,
pub(in crate::tui::app) settings_path: Option<&'a Path>,
pub(in crate::tui::app) snapshot_tool: Option<helix_toolbox_engine::backups::Snapshots>,
pub(in crate::tui::app) snapshots: bool,
pub(in crate::tui::app) evidence: &'a OnceCell<helix_toolbox_engine::reliability::Evidence>,
}
impl View<'_> {
pub(in crate::tui::app) fn evidence(&self) -> helix_toolbox_engine::reliability::Evidence {
evidence(self.evidence, self.catalog)
}
}
pub(in crate::tui::app) fn evidence(
cell: &OnceCell<helix_toolbox_engine::reliability::Evidence>,
catalog: &Catalog,
) -> helix_toolbox_engine::reliability::Evidence {
cell.get_or_init(|| helix_toolbox_engine::reliability::Evidence::load(catalog.root()))
.clone()
}
pub(in crate::tui::app) fn toggle_queued(
queue: &mut Vec<Entry>,
popup: &mut Option<Popup>,
entry: Entry,
) {
if !entry.multi_select {
*popup = Some(Popup::Text(TextPopup::new(
entry.name.clone(),
"This entry runs on its own and cannot be queued with others. Press Enter to run it.",
true,
)));
return;
}
if let Some(position) = queue.iter().position(|other| other.id == entry.id) {
queue.remove(position);
} else {
queue.push(entry);
}
}
pub(in crate::tui::app) struct Read<T> {
receiver: Option<mpsc::Receiver<T>>,
failed: bool,
}
#[derive(Debug)]
pub(in crate::tui::app) struct Unanswered;
impl<T> Default for Read<T> {
fn default() -> Self {
Self {
receiver: None,
failed: false,
}
}
}
impl<T: Send + 'static> Read<T> {
pub(in crate::tui::app) fn start(
&mut self,
host: &Host,
work: impl FnOnce(Host) -> T + Send + 'static,
) {
let (sender, receiver) = mpsc::channel();
let host = host.clone();
let here = host.is_here();
let answer = move || {
let found = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| work(host)));
if let Ok(found) = found {
let _ = sender.send(found);
}
};
if here {
std::thread::spawn(answer);
} else {
answer();
}
self.receiver = Some(receiver);
self.failed = false;
}
}
impl<T> Read<T> {
pub(in crate::tui::app) fn busy(&self) -> bool {
self.receiver.is_some()
}
pub(in crate::tui::app) fn failed(&self) -> bool {
self.failed
}
pub(in crate::tui::app) fn take(&mut self) -> Option<Result<T, Unanswered>> {
match self.receiver.as_ref()?.try_recv() {
Ok(found) => {
self.receiver = None;
Some(Ok(found))
}
Err(mpsc::TryRecvError::Disconnected) => {
self.receiver = None;
self.failed = true;
Some(Err(Unanswered))
}
Err(mpsc::TryRecvError::Empty) => None,
}
}
}
#[cfg(test)]
impl<T> From<mpsc::Receiver<T>> for Read<T> {
fn from(receiver: mpsc::Receiver<T>) -> Self {
Self {
receiver: Some(receiver),
failed: false,
}
}
}