1use crate::{Status, TaskId, internal_state::InternalState};
2use colored::Colorize;
3use std::{sync::mpsc::Sender, time::Duration};
4
5#[derive(Debug, Clone)]
6pub struct SBState {
7 sender: Sender<TaskEvent>,
8}
9
10#[derive(Debug, Clone)]
12pub struct SBStateConfig {
13 pub silent: bool,
16
17 pub refresh_rate: Duration,
19
20 pub max_task_name_width: Option<usize>,
24}
25
26impl Default for SBStateConfig {
27 fn default() -> Self {
28 Self {
29 silent: false,
30 refresh_rate: Duration::from_millis(250),
31 max_task_name_width: None,
32 }
33 }
34}
35
36#[derive(Debug, PartialEq, Eq, Clone)]
38pub(crate) enum TaskEvent {
39 AddTask(TaskId, Option<String>, Status),
40 SetTaskDisplayName(TaskId, String),
41 UpdateTask(TaskId, Status),
42 DeleteTask(TaskId),
43 AddSubTask(TaskId, TaskId, Option<String>, Status),
44 UpdateSubTask(TaskId, TaskId, Status),
45}
46
47impl SBState {
48 pub fn new(config: SBStateConfig) -> Self {
49 let (sender, receiver) = std::sync::mpsc::channel::<TaskEvent>();
50
51 std::thread::spawn(move || -> ! {
52 let mut internal_state = InternalState::default();
53 let mut should_refresh_display = true;
54 loop {
55 for event in receiver.try_iter() {
56 should_refresh_display = true;
57 match event {
58 TaskEvent::AddTask(key, maybe_display_name, status) => {
59 internal_state.add_task(key.make_weak(), maybe_display_name, status);
60 }
61 TaskEvent::SetTaskDisplayName(key, display_name) => {
62 internal_state.set_display_name(key.make_weak(), display_name);
63 }
64 TaskEvent::UpdateTask(key, status) => {
65 internal_state.update_task(key.make_weak(), status);
66 }
67 TaskEvent::DeleteTask(key) => {
68 internal_state.delete_task(key.make_weak());
69 }
70 TaskEvent::AddSubTask(key, subkey, maybe_display_name, status) => {
71 internal_state.add_subtask(
72 key.make_weak(),
73 subkey,
74 maybe_display_name,
75 status,
76 );
77 }
78 TaskEvent::UpdateSubTask(key, subkey, new_status) => {
79 internal_state.update_subtask(key.make_weak(), subkey, new_status);
80 }
81 }
82 }
83
84 if !config.silent && should_refresh_display {
85 print!("{}", termion::clear::All);
87 print!("{}", termion::cursor::Goto(0, 1));
88
89 internal_state.clear_old_entries(
90 std::time::Duration::from_secs(10),
91 &[Status::Error, Status::Info],
92 );
93
94 let num_finished = match internal_state.task_map.get(&Status::Finished) {
95 Some(v) => v.len(),
96 None => 0,
97 };
98
99 println!(
100 "Finished tasks: {} / {}",
101 format!("{}", num_finished).bright_green(),
102 internal_state.get_total(),
103 );
104
105 if let Ok((width, _height)) = termion::terminal_size() {
106 let width = width as usize;
107 let max_task_name_width =
108 config.max_task_name_width.unwrap_or(width / 2).min(width);
109 internal_state.print_simple(Status::Info, 10, |f: &str| f.into(), width);
110 internal_state.print_complex(
111 Status::Started,
112 10,
113 |f: &str| f.bright_green(),
114 width,
115 max_task_name_width,
116 );
117 internal_state.print_complex(
118 Status::Queued,
119 10,
120 |f: &str| f.bright_yellow(),
121 width,
122 max_task_name_width,
123 );
124 internal_state.print_complex(
125 Status::Error,
126 10,
127 |f: &str| f.bright_red(),
128 width,
129 max_task_name_width,
130 );
131 }
132 }
133
134 std::thread::sleep(config.refresh_rate);
135 should_refresh_display = false;
136 }
137 });
138
139 Self { sender }
140 }
141
142 pub fn error<S: ToString>(&self, display_name: S) {
143 let task_id = TaskId::new();
144 self.sender
145 .send(TaskEvent::AddTask(
146 task_id.clone(),
147 Some(display_name.to_string()),
148 Status::Error,
149 ))
150 .unwrap();
151 }
152
153 pub fn info<S: ToString>(&self, display_name: S) {
154 let task_id = TaskId::new();
155 self.sender
156 .send(TaskEvent::AddTask(
157 task_id.clone(),
158 Some(display_name.to_string()),
159 Status::Info,
160 ))
161 .unwrap();
162 }
163
164 pub fn add_task<S: ToString>(&self, display_name: S, status: Status) -> TaskId {
165 let task_id = TaskId::new_with_sender(self.sender.clone());
166 self.sender
167 .send(TaskEvent::AddTask(
168 task_id.clone(),
169 Some(display_name.to_string()),
170 status,
171 ))
172 .unwrap();
173 return task_id;
174 }
175
176 pub fn set_task_display_name(&self, task_id: &TaskId, display_name: String) {
177 self.sender
178 .send(TaskEvent::SetTaskDisplayName(task_id.clone(), display_name))
179 .unwrap();
180 }
181
182 pub fn delete_task(&self, task_id: &TaskId) {
183 self.sender
184 .send(TaskEvent::DeleteTask(task_id.clone()))
185 .unwrap();
186 }
187
188 pub fn update_task(&self, task_id: &TaskId, new_status: Status) {
189 self.sender
190 .send(TaskEvent::UpdateTask(task_id.clone(), new_status))
191 .unwrap();
192 }
193
194 pub fn add_subtask(&self, task_id: &TaskId, status: Status) -> TaskId {
195 let sub_task_id = TaskId::new();
196 self.sender
197 .send(TaskEvent::AddSubTask(
198 task_id.clone(),
199 sub_task_id.clone(),
200 None,
201 status,
202 ))
203 .unwrap();
204 return sub_task_id;
205 }
206
207 pub fn update_subtask(&self, task_id: &TaskId, sub_task_id: &TaskId, status: Status) {
208 self.sender
209 .send(TaskEvent::UpdateSubTask(
210 task_id.clone(),
211 sub_task_id.clone(),
212 status,
213 ))
214 .unwrap();
215 }
216}