use std::sync::Arc;
use crate::entry::Entry;
use crate::error::StorageError;
use crate::repository::{QueryOpts, Repository};
use anyhow::Result;
use chrono::{DateTime, TimeDelta, Utc};
pub enum SessionStatus {
Idle,
Active {
desc: Arc<str>,
started_at: DateTime<Utc>,
},
Paused {
desc: Arc<str>,
},
}
pub struct SessionService<R: Repository> {
repo: R,
status: SessionStatus,
}
impl<R: Repository> SessionService<R> {
pub fn new(repo: R) -> Result<Self> {
let status = {
let entries = repo.list(QueryOpts::default())?;
match entries.last() {
Some(e) if e.interval.end.is_none() => SessionStatus::Active {
desc: e.desc.clone(),
started_at: e.interval.start,
},
_ => SessionStatus::Idle,
}
};
Ok(SessionService { repo, status })
}
pub fn start(&mut self, desc: Arc<str>, now: DateTime<Utc>) -> Result<()> {
self.repo.start_session(desc.clone(), now)?;
self.status = SessionStatus::Active {
desc: desc.clone(),
started_at: now,
};
Ok(())
}
pub fn pause(&mut self, now: DateTime<Utc>) -> Result<()> {
let SessionStatus::Active { desc, .. } = &self.status else {
return Ok(());
};
let desc = desc.clone();
self.repo.end_session(now)?;
self.status = SessionStatus::Paused { desc };
Ok(())
}
pub fn resume(&mut self, now: DateTime<Utc>) -> Result<()> {
let SessionStatus::Paused { desc } = &self.status else {
return Ok(());
};
self.repo.start_session(desc.clone(), now)?;
self.status = SessionStatus::Active {
desc: desc.clone(),
started_at: now,
};
Ok(())
}
pub fn end(&mut self, now: DateTime<Utc>) -> Result<()> {
if !matches!(self.status, SessionStatus::Active { .. }) {
anyhow::bail!("tried to end but nothing was started");
}
self.repo.end_session(now)?;
self.status = SessionStatus::Idle;
Ok(())
}
pub fn rename(&mut self, new_desc: Arc<str>) -> Result<()> {
match &self.status {
SessionStatus::Active { started_at, .. } => {
self.repo.rename_current(new_desc.clone())?;
self.status = SessionStatus::Active {
desc: new_desc.clone(),
started_at: *started_at,
};
}
SessionStatus::Paused { .. } => {
self.status = SessionStatus::Paused {
desc: new_desc.to_owned(),
};
}
SessionStatus::Idle => {}
}
Ok(())
}
pub fn discard_paused(&mut self) {
if matches!(self.status, SessionStatus::Paused { .. }) {
self.status = SessionStatus::Idle;
}
}
pub fn status(&self) -> &SessionStatus {
&self.status
}
pub fn list(&self, opts: QueryOpts) -> Result<Vec<Entry>, StorageError> {
self.repo.list(opts)
}
pub fn flush(&mut self) -> Result<()> {
self.repo.flush()
}
}
pub fn summarize(entries: &[Entry]) -> Vec<(Arc<str>, TimeDelta)> {
use std::collections::HashMap;
let mut map: HashMap<Arc<str>, TimeDelta> = HashMap::new();
for e in entries {
if e.interval.end.is_some() {
*map.entry(e.desc.clone()).or_default() += e.interval.duration();
}
}
let mut result: Vec<(Arc<str>, TimeDelta)> = map.into_iter().map(|(k, v)| (k, v)).collect();
result.sort_by(|a, b| b.1.cmp(&a.1));
result
}