pace_rs/commands/
resume.rs1use abscissa_core::{status_err, tracing::debug, Application, Command, Runnable, Shutdown};
4
5use clap::Parser;
6use eyre::Result;
7
8use pace_cli::{confirmation_or_break, prompt_resume_activity};
9use pace_core::prelude::{
10 get_storage_from_config, ActivityQuerying, ActivityReadOps, ActivityStateManagement,
11 ActivityStore, ResumeCommandOptions, ResumeOptions, SyncStorage, UserMessage,
12};
13use pace_time::{date_time::PaceDateTime, time_zone::PaceTimeZoneKind, Validate};
14
15use crate::prelude::PACE_APP;
16
17#[derive(Command, Debug, Parser)]
19pub struct ResumeCmd {
20 #[clap(flatten)]
21 resume_opts: ResumeCommandOptions,
22}
23
24impl Runnable for ResumeCmd {
25 fn run(&self) {
26 match self.inner_run() {
27 Ok(user_message) => user_message.display(),
28 Err(err) => {
29 status_err!("{}", err);
30 PACE_APP.shutdown(Shutdown::Crash);
31 }
32 };
33 }
34}
35
36impl ResumeCmd {
39 pub fn inner_run(&self) -> Result<UserMessage> {
41 let config = &PACE_APP.config();
42
43 let date_time = PaceDateTime::try_from((
45 self.resume_opts.at().as_ref(),
46 PaceTimeZoneKind::try_from((
47 self.resume_opts.time_zone().as_ref(),
48 self.resume_opts.time_zone_offset().as_ref(),
49 ))?,
50 PaceTimeZoneKind::from(config.general().default_time_zone().as_ref()),
51 ))?
52 .validate()?;
53
54 debug!("Parsed time: {date_time:?}");
55
56 let activity_store =
57 ActivityStore::with_storage(get_storage_from_config(&PACE_APP.config())?)?;
58
59 let (msg, resumed) = if let Some(resumed_activity) = activity_store
60 .resume_most_recent_activity(ResumeOptions::builder().resume_time(date_time).build())?
61 {
62 (format!("Resumed {}", resumed_activity.activity()), true)
63 } else {
64 ("".to_string(), false)
65 };
66
67 let user_message = if *self.resume_opts.list() || !resumed {
69 let Some(activity_ids) = activity_store.list_most_recent_activities(usize::from(
71 PACE_APP
72 .config()
73 .general()
74 .most_recent_count()
75 .unwrap_or_else(|| 9u8),
77 ))?
78 else {
79 return Ok(UserMessage::new("No recent activities to continue."));
80 };
81
82 let activity_items = activity_ids
83 .iter()
84 .flat_map(|activity_id| activity_store.read_activity(*activity_id))
86 .filter_map(|activity| activity.activity().is_resumable().then_some(activity))
87 .collect::<Vec<_>>();
88
89 if activity_items.is_empty() {
90 return Ok(UserMessage::new("No activities to continue."));
91 }
92
93 let string_repr = activity_items
94 .iter()
95 .map(|activity| activity.activity().to_string())
96 .collect::<Vec<_>>();
97
98 let selection = prompt_resume_activity(&string_repr)?;
99
100 let Some(activity_item) = activity_items.get(selection) else {
101 return Ok(UserMessage::new("No activity selected to resume."));
102 };
103
104 let result =
105 activity_store.resume_activity(*activity_item.guid(), ResumeOptions::default());
106
107 let user_message = match result {
108 Ok(_) => format!("Resumed {}", activity_item.activity()),
109 Err(recoverable_err) if recoverable_err.possible_new_activity_from_resume() => {
112 debug!("Activity to resume: {:?}", activity_item.activity());
113
114 confirmation_or_break(
115 "We can't resume this already ended activity. Do you want to begin one with the same contents?",
116 )?;
117
118 debug!("Creating new activity from the same contents");
119
120 let new_activity = activity_item.activity().new_from_self();
121
122 debug!("New Activity: {:?}", new_activity);
123
124 let new_stored_activity = activity_store.begin_activity(new_activity)?;
125
126 debug!("Started Activity: {:?}", new_stored_activity);
127
128 format!("Resumed {}", new_stored_activity.activity())
129 }
130 Err(err) => return Err(err.into()),
131 };
132
133 user_message
134 } else {
135 msg
138 };
139
140 activity_store.sync()?;
141
142 Ok(UserMessage::new(user_message))
143 }
144}