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 => {
86 let start = std::time::Instant::now();
87 terminal.render().await;
88 let duration = start.elapsed();
89 crate::utils::log_perf_event("render", duration);
90 },
91 Action::DebouncedRender => debounce_tx.send(Action::Render).unwrap(),
92 Action::Noop => {},
93 Action::Quit => self.should_quit = true,
94 Action::Suspend => self.should_suspend = true,
95 Action::Resume => self.should_suspend = false,
96 Action::Resize(_, _) => terminal.render().await,
97 Action::EditUnitFile { unit, path } => {
99 event.stop();
100 let mut tui = terminal.tui.lock().await;
101 tui.exit()?;
102
103 let read_unit_file_contents = || match std::fs::read_to_string(&path) {
104 Ok(contents) => contents,
105 Err(e) => {
106 error!("Failed to read unit file `{}`: {}", path, e);
107 "".to_string()
108 },
109 };
110
111 let unit_file_contents = read_unit_file_contents();
112 let editor = std::env::var("EDITOR").unwrap_or_else(|_| "nano".to_string());
113 match Command::new(&editor).arg(&path).status() {
114 Ok(_) => {
115 tui.enter()?;
116 tui.clear()?;
117 event = EventHandler::new(self.home.clone(), action_tx.clone());
118
119 let new_unit_file_contents = read_unit_file_contents();
120 if unit_file_contents != new_unit_file_contents {
121 action_tx.send(Action::ReloadService(unit))?;
122 }
123
124 action_tx.send(Action::EnterMode(Mode::ServiceList))?;
125 },
126 Err(e) => {
127 tui.enter()?;
128 tui.clear()?;
129 event = EventHandler::new(self.home.clone(), action_tx.clone());
130 action_tx.send(Action::EnterError(format!("Failed to open editor `{}`: {}", editor, e)))?;
131 },
132 }
133 },
134 _ => {
135 if let Some(_action) = self.home.lock().await.dispatch(action) {
136 action_tx.send(_action)?
137 };
138 },
139 }
140 }
141 if self.should_suspend {
142 terminal.suspend()?;
143 event.stop();
144 terminal.task.await?;
145 event.task.await?;
146 terminal = TerminalHandler::new(self.home.clone());
147 event = EventHandler::new(self.home.clone(), action_tx.clone());
148 action_tx.send(Action::Resume)?;
149 action_tx.send(Action::Render)?;
150 } else if self.should_quit {
151 terminal.stop()?;
152 event.stop();
153 terminal.task.await?;
154 event.task.await?;
155 break;
156 }
157 }
158 Ok(())
159 }
160}