1use std::{process::Command, sync::Arc};
2
3use anyhow::{Context, Result};
4use log::error;
5use tokio::sync::{mpsc, Mutex};
6use tracing::debug;
7
8use crate::{
9 action::Action,
10 components::{
11 home::{Home, Mode},
12 Component,
13 },
14 event::EventHandler,
15 systemd::{get_all_services, Scope},
16 terminal::TerminalHandler,
17};
18
19pub struct App {
20 pub scope: Scope,
21 pub home: Arc<Mutex<Home>>,
22 pub limit_units: Vec<String>,
23 pub should_quit: bool,
24 pub should_suspend: bool,
25}
26
27impl App {
28 pub fn new(scope: Scope, limit_units: Vec<String>) -> Result<Self> {
29 let home = Home::new(scope, &limit_units);
30 let home = Arc::new(Mutex::new(home));
31 Ok(Self { scope, home, limit_units, should_quit: false, should_suspend: false })
32 }
33
34 pub async fn run(&mut self) -> Result<()> {
35 let (action_tx, mut action_rx) = mpsc::unbounded_channel();
36
37 let (debounce_tx, mut debounce_rx) = mpsc::unbounded_channel();
38
39 let cloned_action_tx = action_tx.clone();
40 tokio::spawn(async move {
41 let debounce_duration = std::time::Duration::from_millis(0);
42 let debouncing = Arc::new(Mutex::new(false));
43
44 loop {
45 let _ = debounce_rx.recv().await;
46
47 if *debouncing.lock().await {
48 continue;
49 }
50
51 *debouncing.lock().await = true;
52
53 let action_tx = cloned_action_tx.clone();
54 let debouncing = debouncing.clone();
55 tokio::spawn(async move {
56 tokio::time::sleep(debounce_duration).await;
57 let _ = action_tx.send(Action::Render);
58 *debouncing.lock().await = false;
59 });
60 }
61 });
62
63 self.home.lock().await.init(action_tx.clone())?;
64
65 let units = get_all_services(self.scope, &self.limit_units)
66 .await
67 .context("Unable to get services. Check that systemd is running and try running this tool with sudo.")?;
68 self.home.lock().await.set_units(units);
69
70 let mut terminal = TerminalHandler::new(self.home.clone());
71 let mut event = EventHandler::new(self.home.clone(), action_tx.clone());
72
73 terminal.render().await;
74
75 loop {
76 if let Some(action) = action_rx.recv().await {
77 match &action {
78 Action::SetLogs { .. } => debug!("action: SetLogs"),
80 Action::SetServices { .. } => debug!("action: SetServices"),
81 _ => debug!("action: {:?}", action),
82 }
83
84 match action {
85 Action::Render => terminal.render().await,
86 Action::DebouncedRender => debounce_tx.send(Action::Render).unwrap(),
87 Action::Noop => {},
88 Action::Quit => self.should_quit = true,
89 Action::Suspend => self.should_suspend = true,
90 Action::Resume => self.should_suspend = false,
91 Action::Resize(_, _) => terminal.render().await,
92 Action::EditUnitFile { unit, path } => {
94 event.stop();
95 let mut tui = terminal.tui.lock().await;
96 tui.exit()?;
97
98 let read_unit_file_contents = || match std::fs::read_to_string(&path) {
99 Ok(contents) => contents,
100 Err(e) => {
101 error!("Failed to read unit file `{path}`: {e}");
102 "".to_string()
103 },
104 };
105
106 let unit_file_contents = read_unit_file_contents();
107 let editor = std::env::var("EDITOR").unwrap_or_else(|_| "nano".to_string());
108 match Command::new(&editor).arg(&path).status() {
109 Ok(_) => {
110 tui.enter()?;
111 tui.clear()?;
112 event = EventHandler::new(self.home.clone(), action_tx.clone());
113
114 let new_unit_file_contents = read_unit_file_contents();
115 if unit_file_contents != new_unit_file_contents {
116 action_tx.send(Action::ReloadService(unit))?;
117 }
118
119 action_tx.send(Action::EnterMode(Mode::ServiceList))?;
120 },
121 Err(e) => {
122 tui.enter()?;
123 tui.clear()?;
124 event = EventHandler::new(self.home.clone(), action_tx.clone());
125 action_tx.send(Action::EnterError(format!("Failed to open editor `{editor}`: {e}")))?;
126 },
127 }
128 },
129 _ => {
130 if let Some(_action) = self.home.lock().await.dispatch(action) {
131 action_tx.send(_action)?
132 };
133 },
134 }
135 }
136 if self.should_suspend {
137 terminal.suspend()?;
138 event.stop();
139 terminal.task.await?;
140 event.task.await?;
141 terminal = TerminalHandler::new(self.home.clone());
142 event = EventHandler::new(self.home.clone(), action_tx.clone());
143 action_tx.send(Action::Resume)?;
144 action_tx.send(Action::Render)?;
145 } else if self.should_quit {
146 terminal.stop()?;
147 event.stop();
148 terminal.task.await?;
149 event.task.await?;
150 break;
151 }
152 }
153 Ok(())
154 }
155}