1use crate::models::{TimePoint, WorkRecord};
8use crate::storage::Storage;
9use anyhow::{Context, Result, anyhow};
10use serde::{Deserialize, Serialize};
11use std::time::Duration as StdDuration;
12use time::{Date, OffsetDateTime};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(rename_all = "lowercase")]
17pub enum TimerStatus {
18 Running,
19 Paused,
20 Stopped,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
28pub struct TimerState {
29 pub id: Option<u32>,
31
32 pub task_name: String,
34
35 pub description: Option<String>,
37
38 pub start_time: OffsetDateTime,
40
41 pub end_time: Option<OffsetDateTime>,
43
44 pub date: Date,
46
47 pub status: TimerStatus,
49
50 pub paused_duration_secs: i64,
52
53 pub paused_at: Option<OffsetDateTime>,
55
56 pub created_at: OffsetDateTime,
58
59 pub updated_at: OffsetDateTime,
61
62 #[serde(default)]
65 pub source_record_id: Option<u32>,
66
67 #[serde(default)]
70 pub source_record_date: Option<Date>,
71}
72
73pub struct TimerManager {
78 storage: Storage,
79}
80
81impl TimerManager {
82 pub fn new(storage: Storage) -> Self {
85 TimerManager { storage }
86 }
87
88 pub fn start(
93 &self,
94 task_name: String,
95 description: Option<String>,
96 source_record_id: Option<u32>,
97 source_record_date: Option<Date>,
98 ) -> Result<TimerState> {
99 if (self.storage.load_active_timer()?).is_some() {
101 return Err(anyhow!("A timer is already running"));
102 }
103
104 let now = OffsetDateTime::now_local()
105 .context("Failed to get local time. System clock may not be configured correctly.")?;
106 let timer = TimerState {
107 id: None,
108 task_name,
109 description,
110 start_time: now,
111 end_time: None,
112 date: now.date(),
113 status: TimerStatus::Running,
114 paused_duration_secs: 0,
115 paused_at: None,
116 created_at: now,
117 updated_at: now,
118 source_record_id,
119 source_record_date,
120 };
121
122 self.storage.save_active_timer(&timer)?;
123 Ok(timer)
124 }
125
126 pub fn stop(&self) -> Result<WorkRecord> {
131 let mut timer = self
132 .storage
133 .load_active_timer()?
134 .ok_or_else(|| anyhow!("No timer is currently running"))?;
135
136 let now = OffsetDateTime::now_local()
137 .context("Failed to get local time. System clock may not be configured correctly.")?;
138
139 let target_date = timer
143 .source_record_date
144 .unwrap_or_else(|| timer.start_time.date());
145
146 timer.end_time = Some(now);
147 timer.status = TimerStatus::Stopped;
148 timer.updated_at = now;
149
150 let mut day_data = self.storage.load(&target_date)?;
152
153 if let Some(source_id) = timer.source_record_id {
156 if let Some(record) = day_data.work_records.get_mut(&source_id) {
158 let end_timepoint = TimePoint::new(now.hour(), now.minute())
160 .map_err(|e| anyhow!(e))
161 .context("Failed to create TimePoint for timer end time")?;
162 record.end = end_timepoint;
163 record.update_duration();
164 } else {
165 let mut work_record = self.to_work_record(timer.clone())?;
167 work_record.id = day_data.next_id();
169 day_data.add_record(work_record);
170 }
171 } else {
172 let mut work_record = self.to_work_record(timer.clone())?;
174 work_record.id = day_data.next_id();
176 day_data.add_record(work_record);
177 }
178
179 self.storage.save(&day_data)?;
180 self.storage.clear_active_timer()?;
181
182 let work_record = self.to_work_record(timer)?;
184 Ok(work_record)
185 }
186
187 pub fn pause(&self) -> Result<TimerState> {
192 let mut timer = self
193 .storage
194 .load_active_timer()?
195 .ok_or_else(|| anyhow!("No timer is currently running"))?;
196
197 if timer.status == TimerStatus::Paused {
198 return Err(anyhow!("Timer is already paused"));
199 }
200
201 if timer.status != TimerStatus::Running {
202 return Err(anyhow!("Can only pause a running timer"));
203 }
204
205 let now = OffsetDateTime::now_local()
206 .context("Failed to get local time. System clock may not be configured correctly.")?;
207 timer.paused_at = Some(now);
208 timer.status = TimerStatus::Paused;
209 timer.updated_at = now;
210
211 self.storage.save_active_timer(&timer)?;
212 Ok(timer)
213 }
214
215 pub fn resume(&self) -> Result<TimerState> {
220 let mut timer = self
221 .storage
222 .load_active_timer()?
223 .ok_or_else(|| anyhow!("No timer is currently running"))?;
224
225 if timer.status != TimerStatus::Paused {
226 return Err(anyhow!("Can only resume a paused timer"));
227 }
228
229 let now = OffsetDateTime::now_local()
230 .context("Failed to get local time. System clock may not be configured correctly.")?;
231
232 if let Some(paused_at) = timer.paused_at {
234 let pause_duration = (now - paused_at).whole_seconds();
235 timer.paused_duration_secs += pause_duration;
236 }
237
238 timer.paused_at = None;
239 timer.status = TimerStatus::Running;
240 timer.updated_at = now;
241
242 self.storage.save_active_timer(&timer)?;
243 Ok(timer)
244 }
245
246 pub fn status(&self) -> Result<Option<TimerState>> {
250 self.storage.load_active_timer()
251 }
252
253 pub fn get_elapsed_duration(&self, timer: &TimerState) -> StdDuration {
257 let end_point = if timer.status == TimerStatus::Paused {
258 timer.paused_at.unwrap_or_else(|| {
260 OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc())
261 })
262 } else {
263 OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc())
265 };
266
267 let elapsed = end_point - timer.start_time;
268 let paused_duration_std = StdDuration::from_secs(timer.paused_duration_secs as u64);
269
270 let elapsed_std = StdDuration::from_secs(elapsed.whole_seconds() as u64)
272 + StdDuration::from_nanos(elapsed.subsec_nanoseconds() as u64);
273
274 elapsed_std
275 .checked_sub(paused_duration_std)
276 .unwrap_or(StdDuration::ZERO)
277 }
278
279 fn to_work_record(&self, timer: TimerState) -> Result<WorkRecord> {
281 if timer.status != TimerStatus::Stopped {
282 return Err(anyhow!("Can only convert stopped timers to WorkRecord"));
283 }
284
285 let start_time = timer.start_time;
286 let end_time = timer
287 .end_time
288 .ok_or_else(|| anyhow!("Stopped timer must have end_time"))?;
289
290 let start_timepoint = TimePoint::new(start_time.hour(), start_time.minute())
292 .map_err(|e| anyhow!(e))
293 .context("Failed to create TimePoint for timer start time")?;
294
295 let end_timepoint = TimePoint::new(end_time.hour(), end_time.minute())
296 .map_err(|e| anyhow!(e))
297 .context("Failed to create TimePoint for timer end time")?;
298
299 let mut record = WorkRecord::new(
300 1, timer.task_name,
302 start_timepoint,
303 end_timepoint,
304 );
305
306 if let Some(description) = timer.description {
307 record.description = description;
308 }
309
310 Ok(record)
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use tempfile::TempDir;
318
319 fn create_test_storage() -> (Storage, TempDir) {
320 let temp_dir = TempDir::new().unwrap();
321 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
322 (storage, temp_dir)
323 }
324
325 #[test]
326 fn test_timer_state_creation() {
327 let now = OffsetDateTime::now_utc();
328 let timer = TimerState {
329 id: None,
330 task_name: "Test Task".to_string(),
331 description: None,
332 start_time: now,
333 end_time: None,
334 date: now.date(),
335 status: TimerStatus::Running,
336 paused_duration_secs: 0,
337 paused_at: None,
338 created_at: now,
339 updated_at: now,
340 source_record_id: None,
341 source_record_date: None,
342 };
343
344 assert_eq!(timer.task_name, "Test Task");
345 assert_eq!(timer.status, TimerStatus::Running);
346 assert_eq!(timer.paused_duration_secs, 0);
347 }
348
349 #[test]
350 fn test_timer_serialization() {
351 let now = OffsetDateTime::now_utc();
352 let timer = TimerState {
353 id: None,
354 task_name: "Test Task".to_string(),
355 description: Some("Test description".to_string()),
356 start_time: now,
357 end_time: None,
358 date: now.date(),
359 status: TimerStatus::Running,
360 paused_duration_secs: 0,
361 paused_at: None,
362 created_at: now,
363 updated_at: now,
364 source_record_id: None,
365 source_record_date: None,
366 };
367
368 let json = serde_json::to_string(&timer).unwrap();
369 let deserialized: TimerState = serde_json::from_str(&json).unwrap();
370
371 assert_eq!(deserialized.task_name, timer.task_name);
372 assert_eq!(deserialized.status, timer.status);
373 }
374
375 #[test]
376 fn test_start_timer() {
377 let (storage, _temp) = create_test_storage();
378 let manager = TimerManager::new(storage);
379
380 let result = manager.start("Work".to_string(), None, None, None);
381 assert!(result.is_ok());
382
383 let timer = result.unwrap();
384 assert_eq!(timer.task_name, "Work");
385 assert_eq!(timer.status, TimerStatus::Running);
386 assert_eq!(timer.paused_duration_secs, 0);
387 }
388
389 #[test]
390 fn test_cannot_start_when_already_running() {
391 let (storage, _temp) = create_test_storage();
392 let manager = TimerManager::new(storage);
393
394 let _ = manager.start("Task 1".to_string(), None, None, None);
395 let result = manager.start("Task 2".to_string(), None, None, None);
396
397 assert!(result.is_err());
398 assert_eq!(
399 result.unwrap_err().to_string(),
400 "A timer is already running"
401 );
402 }
403
404 #[test]
405 fn test_pause_running_timer() {
406 let (storage, _temp) = create_test_storage();
407 let manager = TimerManager::new(storage);
408
409 let _ = manager.start("Work".to_string(), None, None, None);
410 let result = manager.pause();
411
412 assert!(result.is_ok());
413 let timer = result.unwrap();
414 assert_eq!(timer.status, TimerStatus::Paused);
415 assert!(timer.paused_at.is_some());
416 }
417
418 #[test]
419 fn test_cannot_pause_paused_timer() {
420 let (storage, _temp) = create_test_storage();
421 let manager = TimerManager::new(storage);
422
423 let _ = manager.start("Work".to_string(), None, None, None);
424 let _ = manager.pause();
425 let result = manager.pause();
426
427 assert!(result.is_err());
428 }
429
430 #[test]
431 fn test_pause_without_running_timer() {
432 let (storage, _temp) = create_test_storage();
433 let manager = TimerManager::new(storage);
434
435 let result = manager.pause();
436 assert!(result.is_err());
437 }
438
439 #[test]
440 fn test_resume_paused_timer() {
441 let (storage, _temp) = create_test_storage();
442 let manager = TimerManager::new(storage);
443
444 let _ = manager.start("Work".to_string(), None, None, None);
445 let _ = manager.pause();
446 let result = manager.resume();
447
448 assert!(result.is_ok());
449 let timer = result.unwrap();
450 assert_eq!(timer.status, TimerStatus::Running);
451 assert!(timer.paused_at.is_none());
452 }
453
454 #[test]
455 fn test_resume_updates_paused_duration() {
456 let (storage, _temp) = create_test_storage();
457 let manager = TimerManager::new(storage);
458
459 let _ = manager.start("Work".to_string(), None, None, None);
460 let paused1 = manager.pause().unwrap();
461 assert_eq!(paused1.paused_duration_secs, 0);
462
463 let _ = manager.resume();
465 let paused2 = manager.pause().unwrap();
466
467 assert!(paused2.paused_duration_secs >= 0);
469 }
470
471 #[test]
472 fn test_cannot_resume_running_timer() {
473 let (storage, _temp) = create_test_storage();
474 let manager = TimerManager::new(storage);
475
476 let _ = manager.start("Work".to_string(), None, None, None);
477 let result = manager.resume();
478
479 assert!(result.is_err());
480 }
481
482 #[test]
483 fn test_status_returns_none_when_no_timer() {
484 let (storage, _temp) = create_test_storage();
485 let manager = TimerManager::new(storage);
486
487 let result = manager.status().unwrap();
488 assert!(result.is_none());
489 }
490
491 #[test]
492 fn test_status_returns_running_timer() {
493 let (storage, _temp) = create_test_storage();
494 let manager = TimerManager::new(storage);
495
496 let _ = manager.start("Work".to_string(), None, None, None);
497 let result = manager.status().unwrap();
498
499 assert!(result.is_some());
500 let timer = result.unwrap();
501 assert_eq!(timer.task_name, "Work");
502 assert_eq!(timer.status, TimerStatus::Running);
503 }
504
505 #[test]
506 fn test_stop_running_timer() {
507 let (storage, _temp) = create_test_storage();
508 let manager = TimerManager::new(storage);
509
510 let _ = manager.start("Work".to_string(), None, None, None);
511 let result = manager.stop();
512
513 assert!(result.is_ok());
514 let work_record = result.unwrap();
515 assert_eq!(work_record.name, "Work");
516
517 let timer_status = manager.status().unwrap();
519 assert!(timer_status.is_none());
520 }
521
522 #[test]
523 fn test_cannot_stop_without_running_timer() {
524 let (storage, _temp) = create_test_storage();
525 let manager = TimerManager::new(storage);
526
527 let result = manager.stop();
528 assert!(result.is_err());
529 }
530
531 #[test]
532 fn test_stop_returns_work_record_with_description() {
533 let (storage, _temp) = create_test_storage();
534 let manager = TimerManager::new(storage);
535
536 let _ = manager.start(
537 "Work".to_string(),
538 Some("Important task".to_string()),
539 None,
540 None,
541 );
542 let work_record = manager.stop().unwrap();
543
544 assert_eq!(work_record.name, "Work");
545 assert_eq!(work_record.description, "Important task");
546 }
547
548 #[test]
549 fn test_full_timer_lifecycle() {
550 let (storage, _temp) = create_test_storage();
551 let manager = TimerManager::new(storage);
552
553 let started = manager.start("Task".to_string(), None, None, None).unwrap();
555 assert_eq!(started.status, TimerStatus::Running);
556
557 let paused = manager.pause().unwrap();
559 assert_eq!(paused.status, TimerStatus::Paused);
560
561 let resumed = manager.resume().unwrap();
563 assert_eq!(resumed.status, TimerStatus::Running);
564
565 let paused_again = manager.pause().unwrap();
567 assert_eq!(paused_again.status, TimerStatus::Paused);
568
569 let resumed_again = manager.resume().unwrap();
571 assert_eq!(resumed_again.status, TimerStatus::Running);
572
573 let work_record = manager.stop().unwrap();
575 assert_eq!(work_record.name, "Task");
576
577 let status = manager.status().unwrap();
579 assert!(status.is_none());
580 }
581
582 #[test]
583 fn test_get_elapsed_duration_running() {
584 let (storage, _temp) = create_test_storage();
585 let manager = TimerManager::new(storage);
586
587 let timer = manager.start("Task".to_string(), None, None, None).unwrap();
588 let elapsed = manager.get_elapsed_duration(&timer);
589
590 assert!(elapsed.as_secs() < 2);
592 }
593
594 #[test]
595 fn test_get_elapsed_duration_with_pause() {
596 let (storage, _temp) = create_test_storage();
597 let manager = TimerManager::new(storage);
598
599 let _ = manager.start("Task".to_string(), None, None, None);
600 let _ = manager.pause();
601
602 let timer = manager.status().unwrap().unwrap();
603 let elapsed = manager.get_elapsed_duration(&timer);
604
605 assert!(elapsed.as_secs() < 2);
607 }
608
609 #[test]
610 fn test_stop_updates_existing_record() {
611 use crate::models::DayData;
612 use crate::models::TimePoint;
613 use crate::models::WorkRecord;
614 use tempfile::TempDir;
615 use time::OffsetDateTime;
616
617 let temp_dir = TempDir::new().unwrap();
619 let storage_path = temp_dir.path().to_path_buf();
620
621 let now = OffsetDateTime::now_utc();
623 let today = now.date();
624 let mut day_data = DayData::new(today);
625
626 let record = WorkRecord::new(
627 1,
628 "Existing Task".to_string(),
629 TimePoint::new(9, 0).unwrap(),
630 TimePoint::new(10, 0).unwrap(),
631 );
632 day_data.add_record(record);
633
634 let storage1 = Storage::new_with_dir(storage_path.clone()).unwrap();
636 storage1.save(&day_data).unwrap();
637
638 let manager = TimerManager::new(storage1);
640 manager
641 .start("Existing Task".to_string(), None, Some(1), Some(today))
642 .unwrap();
643
644 manager.stop().unwrap();
646
647 let storage2 = Storage::new_with_dir(storage_path).unwrap();
649 let updated_day_data = storage2.load(&today).unwrap();
650
651 assert_eq!(updated_day_data.work_records.len(), 1);
653
654 let updated_record = updated_day_data.work_records.get(&1).unwrap();
656 assert_eq!(updated_record.name, "Existing Task");
657 assert!(
659 updated_record.end.hour >= now.hour()
660 || (updated_record.end.hour == 0 && now.hour() == 23)
661 ); }
663}