Skip to content

Commit

Permalink
start implementing hold and continue for intermissions
Browse files Browse the repository at this point in the history
Signed-off-by: simonsan <[email protected]>
  • Loading branch information
simonsan committed Feb 22, 2024
1 parent fb9b5ec commit 2f670a4
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 85 deletions.
68 changes: 50 additions & 18 deletions crates/core/src/domain/activity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,48 @@ pub enum ActivityKind {
PomodoroIntermission,
}

impl ActivityKind {
/// Returns `true` if the activity kind is [`Activity`].
///
/// [`Activity`]: ActivityKind::Activity
#[must_use]
pub fn is_activity(&self) -> bool {
matches!(self, Self::Activity)
}

/// Returns `true` if the activity kind is [`Task`].
///
/// [`Task`]: ActivityKind::Task
#[must_use]
pub fn is_task(&self) -> bool {
matches!(self, Self::Task)
}

/// Returns `true` if the activity kind is [`Intermission`].
///
/// [`Intermission`]: ActivityKind::Intermission
#[must_use]
pub fn is_intermission(&self) -> bool {
matches!(self, Self::Intermission)
}

/// Returns `true` if the activity kind is [`PomodoroWork`].
///
/// [`PomodoroWork`]: ActivityKind::PomodoroWork
#[must_use]
pub fn is_pomodoro_work(&self) -> bool {
matches!(self, Self::PomodoroWork)
}

/// Returns `true` if the activity kind is [`PomodoroIntermission`].
///
/// [`PomodoroIntermission`]: ActivityKind::PomodoroIntermission
#[must_use]
pub fn is_pomodoro_intermission(&self) -> bool {
matches!(self, Self::PomodoroIntermission)
}
}

/// The cycle of pomodoro activity a user can track
// TODO!: Optional: Track Pomodoro work/break cycles
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
Expand Down Expand Up @@ -150,6 +192,14 @@ pub struct ActivityKindOptions {
parent_id: Option<ActivityGuid>,
}

impl ActivityKindOptions {
pub fn new(parent_id: impl Into<Option<ActivityGuid>>) -> Self {
Self {
parent_id: parent_id.into(),
}
}
}

impl Default for Activity {
fn default() -> Self {
Self {
Expand Down Expand Up @@ -246,24 +296,6 @@ impl Activity {
self.end_activity(end_opts);
Ok(())
}

// pub fn start_intermission(&mut self, date: NaiveDate, time: NaiveTime) {
// let new_intermission = IntermissionPeriod::new(date, time);
// if let Some(ref mut periods) = self.intermission_periods {
// periods.push(new_intermission);
// } else {
// self.intermission_periods = Some(vec![new_intermission]);
// }
// }

// pub fn end_intermission(&mut self, date: NaiveDate, time: NaiveTime) {
// if let Some(intermission_periods) = &mut self.intermission_periods {
// if let Some(last_period) = intermission_periods.last_mut() {
// // Assuming intermissions can't overlap, the last one is the one to end
// last_period.end(date, time);
// }
// }
// }
}

#[cfg(test)]
Expand Down
8 changes: 8 additions & 0 deletions crates/core/src/domain/activity_log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ pub struct ActivityLog {
activities: VecDeque<Activity>,
}

impl std::ops::Deref for ActivityLog {
type Target = VecDeque<Activity>;

fn deref(&self) -> &Self::Target {
&self.activities
}
}

impl FromIterator<Activity> for ActivityLog {
fn from_iter<T: IntoIterator<Item = Activity>>(iter: T) -> Self {
Self {
Expand Down
41 changes: 2 additions & 39 deletions crates/core/src/domain/intermission.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,5 @@
//! Intermission entity and business logic
use chrono::{Local, NaiveDateTime};
use serde_derive::{Deserialize, Serialize};
use crate::Activity;

use crate::domain::time::PaceDuration;

#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
pub struct IntermissionPeriod {
begin: NaiveDateTime,
end: Option<NaiveDateTime>,
duration: Option<PaceDuration>,
}

impl Default for IntermissionPeriod {
fn default() -> Self {
Self {
begin: Local::now().naive_local(),
end: None,
duration: None,
}
}
}

impl IntermissionPeriod {
pub fn new(
begin: NaiveDateTime,
end: Option<NaiveDateTime>,
duration: Option<PaceDuration>,
) -> Self {
Self {
begin,
end,
duration,
}
}

pub fn end(&mut self, end: NaiveDateTime) {
// TODO!: Calculate duration
self.end = Some(end);
}
}
impl Activity {}
9 changes: 9 additions & 0 deletions crates/core/src/domain/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,15 @@ impl From<NaiveDateTime> for BeginDateTime {
}
}

impl From<Option<NaiveDateTime>> for BeginDateTime {
fn from(time: Option<NaiveDateTime>) -> Self {
match time {
Some(time) => Self(time),
None => Self::default(),
}
}
}

/// Calculate the duration of the activity
///
/// # Arguments
Expand Down
2 changes: 2 additions & 0 deletions crates/core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ pub enum PaceErrorKind {
DatabaseStorageNotImplemented,
/// Failed to parse time '{0}' from user input, please use the format HH:MM
ParsingTimeFromUserInputFailed(String),
/// There is no path available to store the activity log
NoPathAvailable,
}

/// [`ActivityLogErrorKind`] describes the errors that can happen while dealing with the activity log.
Expand Down
18 changes: 12 additions & 6 deletions crates/core/src/service/activity_store.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use std::collections::{BTreeMap, HashSet, VecDeque};

use chrono::NaiveDateTime;
use chrono::{prelude::NaiveDate, NaiveDateTime};
use serde_derive::{Deserialize, Serialize};

use crate::{
domain::{
activity::{Activity, ActivityGuid},
activity_log::ActivityLog,
filter::FilteredActivities,
},
error::{PaceOptResult, PaceResult},
Expand Down Expand Up @@ -121,13 +122,18 @@ impl ActivityStateManagement for ActivityStore {
impl ActivityQuerying for ActivityStore {
fn find_activities_in_date_range(
&self,
_start_date: chrono::prelude::NaiveDate,
_end_date: chrono::prelude::NaiveDate,
) -> PaceResult<crate::domain::activity_log::ActivityLog> {
todo!("Implement find_activities_in_date_range for ActivityStore")
start_date: NaiveDate,
end_date: NaiveDate,
) -> PaceResult<ActivityLog> {
self.storage
.find_activities_in_date_range(start_date, end_date)
}

fn list_activities_by_id(&self) -> PaceOptResult<BTreeMap<ActivityGuid, Activity>> {
todo!("Implement list_activities_by_id for ActivityStore")
self.storage.list_activities_by_id()
}

fn latest_active_activity(&self) -> PaceOptResult<Activity> {
self.storage.latest_active_activity()
}
}
12 changes: 12 additions & 0 deletions crates/core/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,18 @@ pub trait ActivityQuerying: ActivityReadOps {
/// A collection of the activities that were loaded from the storage backend by their ID in a `BTreeMap`.
/// If no activities are found, it should return `Ok(None)`.
fn list_activities_by_id(&self) -> PaceOptResult<BTreeMap<ActivityGuid, Activity>>;

/// Get the latest active activity.
///
/// # Errors
///
/// This function should return an error if the activity cannot be loaded.
///
/// # Returns
///
/// The latest active activity.
/// If no activity is found, it should return `Ok(None)`.
fn latest_active_activity(&self) -> PaceOptResult<Activity>;
}

/// Tagging Activities
Expand Down
13 changes: 9 additions & 4 deletions crates/core/src/storage/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,19 @@ impl ActivityWriteOps for TomlActivityStorage {

impl ActivityQuerying for TomlActivityStorage {
fn list_activities_by_id(&self) -> PaceOptResult<BTreeMap<ActivityGuid, Activity>> {
todo!("Implement `activities_by_id` for `TomlActivityStorage`")
self.cache.list_activities_by_id()
}

fn find_activities_in_date_range(
&self,
_start_date: NaiveDate,
_end_date: NaiveDate,
start_date: NaiveDate,
end_date: NaiveDate,
) -> PaceResult<ActivityLog> {
todo!("Implement `find_activities_in_date_range` for `TomlActivityStorage`")
self.cache
.find_activities_in_date_range(start_date, end_date)
}

fn latest_active_activity(&self) -> PaceOptResult<Activity> {
self.cache.latest_active_activity()
}
}
24 changes: 21 additions & 3 deletions crates/core/src/storage/in_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ pub struct InMemoryActivityStorage {

impl From<ActivityLog> for InMemoryActivityStorage {
fn from(activities: ActivityLog) -> Self {
Self {
activities: Arc::new(Mutex::new(activities)),
}
Self::new_with_activity_log(activities)
}
}

Expand Down Expand Up @@ -359,4 +357,24 @@ impl ActivityQuerying for InMemoryActivityStorage {
) -> PaceOptResult<std::collections::BTreeMap<ActivityGuid, Activity>> {
todo!("Implement list_activities_by_id for InMemoryActivityStorage")
}

fn latest_active_activity(&self) -> PaceOptResult<Activity> {
let Ok(activities) = self.activities.lock() else {
return Err(ActivityLogErrorKind::MutexHasBeenPoisoned.into());
};

let activity = activities
.activities()
.par_iter()
.find_first(|activity| {
activity.is_active()
&& !activity.kind().is_intermission()
&& !activity.kind().is_pomodoro_intermission()
})
.cloned();

drop(activities);

Ok(activity)
}
}
31 changes: 30 additions & 1 deletion crates/core/tests/activity_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use chrono::{Local, NaiveDateTime};
use pace_core::{
Activity, ActivityFilter, ActivityGuid, ActivityLog, ActivityReadOps, ActivityStore,
ActivityWriteOps, BeginDateTime, InMemoryActivityStorage, TestResult,
ActivityWriteOps, BeginDateTime, InMemoryActivityStorage, PaceResult, TestResult,
};
use rstest::{fixture, rstest};
use similar_asserts::assert_eq;
Expand Down Expand Up @@ -301,3 +301,32 @@ fn test_activity_store_update_activity_fails(

assert!(store.update_activity(activity_id, new_activity).is_err());
}

#[rstest]
fn test_activity_store_begin_intermission_passes() -> PaceResult<()> {
let toml_string = r#"
[[activities]]
id = "01HQ8B27751H7QPBD2V7CZD1B7"
description = "Intermission Test"
begin = "2024-02-22T13:01:25"
kind = "intermission"
parent-id = "01HQ8B1WS5X0GZ660738FNED91"
[[activities]]
id = "01HQ8B1WS5X0GZ660738FNED91"
category = "MyCategory::SubCategory"
description = "Intermission Test"
begin = "2024-02-22T13:01:14"
kind = "activity"
"#;

let activity_log = toml::from_str::<ActivityLog>(toml_string)?;

let _store = ActivityStore::new(Box::new(InMemoryActivityStorage::new_with_activity_log(
activity_log,
)));

// TODO!: Implement intermission handling.

Ok(())
}
Loading

0 comments on commit 2f670a4

Please sign in to comment.