main.rs
1 use dtasks::{DurationsConf, Task, TimerEnds, hms_to_seconds, seconds_to_hms}; 2 use std::collections::HashMap; 3 use std::io::Write; 4 use std::io::{self, Read, stdout}; 5 use std::sync::mpsc::{self, Receiver}; 6 use std::thread; 7 use std::time::Duration; 8 9 // NOTE: 10 // Guideline: Follow the Chapter 12 rustbook development procedure. 11 // After rebuild on a crates base to increase Code Quality making it 12 // more scalable and maintainable. And last, do some implementations 13 // for learning concepts such as WASM, libp2p and concurrency. 14 15 // TODO: 16 // - [ ] Reorganize modules of lib crate 17 // - [ ] Refactor and cleanup Tasks 18 // - [ ] create logic for export and import trasks to filesystem 19 // - [ ] allow numbers to add and remove tasks and select task to work on 20 // - [ ] fix panics with error handling 21 // - [ ] Add functions for the menu 22 // - [ ] Add implementation for update tasks struct data. 23 // work_on_task(): 24 // - [ ] refactor to manage breaks properly 25 // - [ ] Add section if breaks are required, create as tasks::new_break and the logic 26 // list_tasks(): 27 // - [ ] Add display trait for tasks 28 // - [ ] Do with closures if better 29 // 30 // HACK: Future Development 31 // * Use clap to work as autocomplete terminal (VIM Bindings Optional) 32 // * Implement a format trait or Substitute time std with chrono 33 // * WASM support and a web interface 34 // * libp2p integration for handling it as a DAPP 35 36 fn main() { 37 let mut tasks = HashMap::new(); 38 tasks.insert("test".to_string(), Task::new()); 39 40 let mut input = String::new(); 41 42 print_tasks_menu(); 43 44 loop { 45 print!("main> "); 46 stdout().flush().expect("could not clean"); 47 input.clear(); 48 49 io::stdin() 50 .read_line(&mut input) 51 .expect("Failed to read line"); 52 match input.as_str() { 53 "a\n" => add_task(&mut tasks), 54 "r\n" => rm_task(&mut tasks), 55 "l\n" => list_tasks(&tasks), 56 "w\n" => work_on_task(&mut tasks), 57 "?\n" => print_tasks_menu(), 58 "q\n" => break, 59 _ => { 60 print!("{esc}[2J{esc}[1;1H", esc = 27 as char); 61 println!("Unkown Input"); 62 println!("press ? for help"); 63 continue; 64 } 65 } 66 } 67 } 68 69 fn print_tasks_menu() { 70 print!("{esc}[2J{esc}[1;1H", esc = 27 as char); 71 println!("Select one of the following options"); 72 println!("a: adds/edits a task"); 73 println!("r: removes a task"); 74 println!("l: lists the tasks"); 75 println!("w: work on a task"); 76 println!("q: quit the program"); 77 println!("?: for help (this menu)"); 78 println!(""); 79 } 80 fn print_task_menu() { 81 print!("{esc}[2J{esc}[1;1H", esc = 27 as char); 82 println!("input one of the following options"); 83 println!("d: display the current timer"); 84 println!("l: loop display the current timer"); 85 println!("s: start,stop,resume the timer"); 86 println!("n: next timer"); 87 println!("p: previous timer"); 88 println!("q: quit this task"); 89 println!("?: for help (this menu)"); 90 println!(""); 91 } 92 93 fn add_task(tasks: &mut HashMap<String, Task>) { 94 print!("{esc}[2J{esc}[1;1H", esc = 27 as char); 95 let mut task_name = String::new(); 96 let mut num_pomodoros = String::new(); 97 98 // some variables for custom pomodoros 99 let mut duration = DurationsConf::new(); 100 let mut opt = String::new(); 101 let mut pomodoro: u64 = 25; 102 let mut short_break: u64 = 5; 103 let mut long_break: u64 = 15; 104 105 println!("Input the task name:"); 106 io::stdin() 107 .read_line(&mut task_name) 108 .expect("Failed to read line"); 109 task_name = task_name.trim().to_string(); 110 111 println!("Input how many pomodoros:"); 112 io::stdin() 113 .read_line(&mut num_pomodoros) 114 .expect("Failed to read line"); 115 let num_pomodoros: usize = match num_pomodoros.trim().parse() { 116 Ok(num) => num, 117 Err(_) => 1, // 1 pomo as default if error 118 }; 119 120 println!("Set custom pomodoro timer? [y/n]:"); 121 122 io::stdin() 123 .read_line(&mut opt) 124 .expect("Failed to read line"); 125 match opt.as_str() { 126 "y\n" => { 127 println!("input minutes for pomodoro:"); 128 stdout().flush().expect("could not clean"); 129 opt.clear(); 130 io::stdin() 131 .read_line(&mut opt) 132 .expect("Failed to read line"); 133 pomodoro = match opt.trim().parse() { 134 Ok(num) => num, 135 Err(_) => 25, // 1 pomo as default if error 136 }; 137 println!("input minutes for short break:"); 138 stdout().flush().expect("could not clean"); 139 opt.clear(); 140 io::stdin() 141 .read_line(&mut opt) 142 .expect("Failed to read line"); 143 short_break = match opt.trim().parse() { 144 Ok(num) => num, 145 Err(_) => 5, // 1 pomo as default if error 146 }; 147 if num_pomodoros > 1 { 148 println!("input minutes for long break:"); 149 stdout().flush().expect("could not clean"); 150 opt.clear(); 151 io::stdin() 152 .read_line(&mut opt) 153 .expect("Failed to read line"); 154 long_break = match opt.trim().parse() { 155 Ok(num) => num, 156 Err(_) => 15, // 1 pomo as default if error 157 }; 158 duration.update_vals( 159 num_pomodoros, 160 hms_to_seconds(0, pomodoro, 0), 161 hms_to_seconds(0, short_break, 0), 162 hms_to_seconds(0, long_break, 0), 163 ); 164 } else { 165 // No long break neded 166 long_break = 0; 167 duration.update_vals( 168 num_pomodoros, 169 hms_to_seconds(0, pomodoro, 0), 170 hms_to_seconds(0, short_break, 0), 171 hms_to_seconds(0, long_break, 0), 172 ); 173 } 174 } 175 _ => { 176 println!("pressed other, so settiing pomodoro to default time"); 177 duration.update_vals( 178 num_pomodoros, 179 hms_to_seconds(0, pomodoro, 0), 180 hms_to_seconds(0, short_break, 0), 181 hms_to_seconds(0, long_break, 0), 182 ); 183 println!("press ? for help"); 184 } 185 } 186 187 // ISSUE: Check for entry.or_insert for error handling 188 tasks.insert(task_name.to_string(), Task::build(0, duration)); 189 190 list_tasks(tasks); 191 } 192 193 fn rm_task(tasks: &mut HashMap<String, Task>) { 194 list_tasks(tasks); 195 let mut task_name = String::new(); 196 197 println!("Input the task name to remove it:"); 198 io::stdin() 199 .read_line(&mut task_name) 200 .expect("Failed to read line"); 201 task_name = task_name.trim().to_string(); 202 203 // TODO: Simplify 204 if let Some(tsk) = tasks.remove(&mut task_name) { 205 list_tasks(tasks); 206 match tsk.get_pomodoro_ends_as_secs() { 207 Ok(secs) => { 208 let (hh, mm, ss) = seconds_to_hms(secs); 209 println!( 210 "{task_name}: Duration: {hh}:{mm}:{ss} ToDo: {} Done:{} REMOVED", 211 tsk.get_pomodoro_todo(), 212 tsk.get_pomodoro_done() 213 ); 214 } 215 Err(e) => { 216 println!( 217 "{task_name}: Duration: {e} ToDo: {} Done:{} REMOVED ", 218 tsk.get_pomodoro_todo(), 219 tsk.get_pomodoro_done() 220 ); 221 } 222 } 223 } else { 224 //Do error handling 225 println!("there is not such task"); 226 } 227 } 228 229 fn list_tasks(tasks: &HashMap<String, Task>) { 230 print!("{esc}[2J{esc}[1;1H", esc = 27 as char); 231 if tasks.len() > 0 { 232 println!("Tasks:"); 233 for task in tasks.keys() { 234 if let Some(tsk) = tasks.get(task) { 235 match tsk.get_pomodoro_ends_as_secs() { 236 Ok(secs) => { 237 let (hh, mm, ss) = seconds_to_hms(secs); 238 println!( 239 "{task}: Duration: {hh}:{mm}:{ss} ToDo: {} Done:{}", 240 tsk.get_pomodoro_todo(), 241 tsk.get_pomodoro_done() 242 ); 243 } 244 Err(e) => { 245 println!( 246 "{task}: Duration: {e} ToDo: {} Done:{}", 247 tsk.get_pomodoro_todo(), 248 tsk.get_pomodoro_done() 249 ); 250 } 251 } 252 } 253 } 254 } else { 255 println!("Not added tasks, press a for adding one!"); 256 } 257 println!(""); 258 } 259 260 fn work_on_task(tasks: &mut HashMap<String, Task>) { 261 list_tasks(tasks); 262 print!("Input the task name to work on: "); 263 let mut task_name = String::new(); 264 // ToDO handle input errors 265 print!("> "); 266 stdout().flush().expect("could not clean"); 267 task_name.clear(); 268 269 io::stdin() 270 .read_line(&mut task_name) 271 .expect("Failed to read line"); 272 task_name = task_name.trim().to_string(); 273 274 if let Some(task) = tasks.get_mut(&task_name) { 275 if task.get_pomodoro_todo() > 0 { 276 print_task_menu(); 277 loop { 278 let mut input = String::new(); 279 print!("working on {task_name}> "); 280 stdout().flush().expect("could not clean"); 281 input.clear(); 282 283 io::stdin() 284 .read_line(&mut input) 285 .expect("Failed to read line"); 286 287 match input.as_str() { 288 "d\n" => { 289 print!("{esc}[2J{esc}[1;1H", esc = 27 as char); 290 print_timer(&task_name, task); 291 } 292 "l\n" => { 293 let input_rx = spawn_input_listener(); 294 loop { 295 print!("{esc}[2J{esc}[1;1H", esc = 27 as char); 296 print_timer(&task_name, task); 297 println!("Press any key (then Enter) to stop the display loop."); 298 // Check if we received the interrupt signal. 299 // `try_recv` is non‑blocking; it returns immediately. 300 if let Ok(_) = input_rx.try_recv() { 301 println!("Got a key press → stopping pomodoro display"); 302 break; 303 } 304 // Avoid a busy‑spin – sleep a little. 305 thread::sleep(Duration::from_millis(200)); 306 // if task is ended no need to keep the display loop 307 if task.is_ended() { 308 println!("Current timer is ended, loop display stopped."); 309 break; 310 } 311 } 312 } 313 "s\n" => { 314 print!("{esc}[2J{esc}[1;1H", esc = 27 as char); 315 let _ = task.start_pause_pomodoro(); 316 print_timer(&task_name, task); 317 } 318 "n\n" => { 319 print!("{esc}[2J{esc}[1;1H", esc = 27 as char); 320 task.next_pomodoro(); 321 print_timer(&task_name, task); 322 } 323 "p\n" => { 324 print!("{esc}[2J{esc}[1;1H", esc = 27 as char); 325 task.previous_pomodoro(); 326 print_timer(&task_name, task); 327 } 328 "?\n" => print_task_menu(), 329 "q\n" => break, 330 _ => { 331 print!("{esc}[2J{esc}[1;1H", esc = 27 as char); 332 println!("Unkown Input"); 333 println!("press ? for help"); 334 continue; 335 } 336 } 337 } 338 } else { 339 print!("{esc}[2J{esc}[1;1H", esc = 27 as char); 340 println!("the current task has no pomodoros todo") 341 } 342 } else { 343 print!("{esc}[2J{esc}[1;1H", esc = 27 as char); 344 println!("There is not a task named: {task_name}") 345 } 346 } 347 348 pub fn print_timer(task_name: &str, task: &mut Task) { 349 let (timer_state, timer_time, paused_time, idx, timer_type) = task.get_pomodoro_data(); 350 let timer_type = match timer_type { 351 TimerEnds::Pomodoro(_) => "Pomorodo", 352 TimerEnds::ShortBreak(_) => "Short Break", 353 TimerEnds::LongBreak(_) => "Long Break", 354 }; 355 print!("{esc}[2J{esc}[1;1H", esc = 27 as char); 356 println!("\t----- {} -----", task_name); 357 println!("Timer: {}", timer_type); 358 println!("State: {}", timer_state); 359 let (mut hh, mut mm, mut ss) = seconds_to_hms(timer_time); 360 // TODO: set this as a Display trait of type hh_mm_ss and verify conditional logic 361 if hh == 0 { 362 println!("Time to finish: {mm}:{ss}"); 363 } else { 364 println!("Time to finish: {hh}:{mm}:{ss}"); 365 } 366 (hh, mm, ss) = seconds_to_hms(paused_time); 367 if hh == 0 { 368 println!("Paused Time: {mm}:{ss}"); 369 } else { 370 println!("Paused Time: {hh}:{mm}:{ss}"); 371 } 372 println!("Cycle: {}/{}", idx + 1, task.get_task_cycle().len()); 373 // TODO: better information of working tasks and timer_type 374 // perhaps add into task menu? 375 // match timer_type { 376 // "Pomodoro" => println!("Doing:{}",), 377 // "Short Break" => println!() 378 //} 379 } 380 381 /// Starts a background thread that reads *one* byte from stdin and 382 /// sends a message on the channel when something is typed. 383 /// 384 /// The channel is unbounded – we only care about the *first* key press. 385 fn spawn_input_listener() -> Receiver<()> { 386 let (tx, rx) = mpsc::channel::<()>(); 387 388 thread::spawn(move || { 389 // `stdin()` gives us a handle that is buffered by default. 390 // We want to read a single byte, so we lock it and read directly. 391 let stdin = io::stdin(); 392 let mut handle = stdin.lock(); 393 394 // This will block until the user presses a key and hits <Enter>. 395 // `read_exact` guarantees we get exactly one byte. 396 let mut buf = [0u8; 1]; 397 let _ = handle.read_exact(&mut buf); 398 399 // Send the signal. If the receiver has dropped (e.g. main finished) 400 // we just ignore the error. 401 let _ = tx.send(()); 402 }); 403 404 rx 405 } 406 407 // Structural Changes ToDo and Ideas: 408 409 //enum TaskOption { 410 // Start, 411 // Stop, 412 // Pause(Task), 413 // Update(Task), 414 //} 415 416 //struct Tasks(HashMap<String, Task>); 417 // 418 //impl Tasks { 419 // fn Add(mut self, Task) { 420 // } 421 // fn Remove(mut self, Task) { 422 // } 423 // fn List(&self) { 424 // } 425 //}