From 3a7d995c81c31c149fb1ad9e4fda3d443915b694 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:43:26 +0100 Subject: [PATCH] refactoring storage Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- Cargo.lock | 1 + crates/core/Cargo.toml | 1 + crates/core/src/commands/adjust.rs | 14 +- crates/core/src/commands/begin.rs | 11 +- crates/core/src/commands/end.rs | 12 +- crates/core/src/commands/hold.rs | 23 +- crates/core/src/commands/now.rs | 41 +-- crates/core/src/commands/reflect.rs | 10 +- crates/core/src/config.rs | 1 - crates/core/src/error.rs | 43 ++-- crates/core/src/lib.rs | 4 +- crates/core/src/service/activity_store.rs | 66 ++--- crates/core/src/service/activity_tracker.rs | 4 +- crates/core/src/storage.rs | 88 ++++--- crates/storage/Cargo.toml | 1 + crates/storage/src/error.rs | 14 +- crates/storage/src/file.rs | 67 ++--- crates/storage/src/in_memory.rs | 68 ++--- crates/storage/src/lib.rs | 91 ++++--- crates/storage/src/sqlite.rs | 266 +++----------------- crates/storage/src/storage_types.rs | 240 ++++++++++++++++++ 21 files changed, 594 insertions(+), 472 deletions(-) create mode 100644 crates/storage/src/storage_types.rs diff --git a/Cargo.lock b/Cargo.lock index a041b51f..822606c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1336,6 +1336,7 @@ version = "0.1.0" dependencies = [ "chrono", "displaydoc", + "eyre", "itertools", "libsqlite3-sys", "merge", diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 773b9221..74733e87 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -31,6 +31,7 @@ clap = { workspace = true, optional = true, features = ["env", "wrap_help", "der directories = { workspace = true } displaydoc = { workspace = true } dotenvy = { workspace = true } +eyre = { workspace = true } getset = { workspace = true } itertools = { workspace = true } merge = { workspace = true } diff --git a/crates/core/src/commands/adjust.rs b/crates/core/src/commands/adjust.rs index 0c1149b8..60e68c53 100644 --- a/crates/core/src/commands/adjust.rs +++ b/crates/core/src/commands/adjust.rs @@ -13,7 +13,7 @@ use crate::{ commands::UpdateOptions, config::PaceConfig, error::{ActivityLogErrorKind, PaceResult, UserMessage}, - prelude::ActivityStorage, + prelude::{ActivityStorage, PaceErrorKind}, service::activity_store::ActivityStore, storage::{ActivityQuerying, ActivityWriteOps, SyncStorage}, }; @@ -165,10 +165,12 @@ impl AdjustCommandOptions { debug!("Parsed time: {date_time:?}"); - let activity_store = ActivityStore::with_storage(storage)?; + let activity_store = + ActivityStore::with_storage(storage).map_err(PaceErrorKind::Storage)?; let activity_item = activity_store - .most_recent_active_activity()? + .most_recent_active_activity() + .map_err(PaceErrorKind::Storage)? .ok_or_else(|| ActivityLogErrorKind::NoActiveActivityToAdjust)?; debug!("Most recent active activity item: {:?}", activity_item); @@ -212,10 +214,12 @@ impl AdjustCommandOptions { } } - _ = activity_store.update_activity(guid, activity.clone(), UpdateOptions::default())?; + _ = activity_store + .update_activity(guid, activity.clone(), UpdateOptions::default()) + .map_err(PaceErrorKind::Storage)?; if activity_item.activity() != &activity { - activity_store.sync()?; + activity_store.sync().map_err(PaceErrorKind::Storage)?; return Ok(UserMessage::new(format!( "{} has been adjusted.", activity_item.activity() diff --git a/crates/core/src/commands/begin.rs b/crates/core/src/commands/begin.rs index ff235469..86181d0a 100644 --- a/crates/core/src/commands/begin.rs +++ b/crates/core/src/commands/begin.rs @@ -12,7 +12,7 @@ use crate::{ config::PaceConfig, domain::activity::{Activity, ActivityKind}, error::{PaceResult, UserMessage}, - prelude::ActivityStorage, + prelude::{ActivityStorage, PaceErrorKind}, service::activity_store::ActivityStore, storage::{ActivityStateManagement, SyncStorage}, }; @@ -144,13 +144,16 @@ impl BeginCommandOptions { .tags(tags) .build(); - let activity_store = ActivityStore::with_storage(storage)?; + let activity_store = + ActivityStore::with_storage(storage).map_err(PaceErrorKind::Storage)?; - let activity_item = activity_store.begin_activity(activity)?; + let activity_item = activity_store + .begin_activity(activity) + .map_err(PaceErrorKind::Storage)?; debug!("Started Activity: {:?}", activity_item); - activity_store.sync()?; + activity_store.sync().map_err(PaceErrorKind::Storage)?; Ok(UserMessage::new(format!("{}", activity_item.activity()))) } diff --git a/crates/core/src/commands/end.rs b/crates/core/src/commands/end.rs index f1c3a96b..d9b7b85c 100644 --- a/crates/core/src/commands/end.rs +++ b/crates/core/src/commands/end.rs @@ -13,7 +13,7 @@ use crate::{ commands::EndOptions, config::PaceConfig, error::{PaceResult, UserMessage}, - prelude::ActivityStorage, + prelude::{ActivityStorage, PaceErrorKind}, service::activity_store::ActivityStore, storage::{ActivityStateManagement, SyncStorage}, }; @@ -98,11 +98,15 @@ impl EndCommandOptions { debug!("Parsed date time: {:?}", date_time); - let activity_store = ActivityStore::with_storage(storage)?; + let activity_store = + ActivityStore::with_storage(storage).map_err(PaceErrorKind::Storage)?; let end_opts = EndOptions::builder().end_time(date_time).build(); - let user_message = (activity_store.end_all_activities(end_opts)?).map_or_else( + let user_message = (activity_store + .end_all_activities(end_opts) + .map_err(PaceErrorKind::Storage)?) + .map_or_else( || "No unfinished activities to end.".to_string(), |unfinished_activities| { let mut msgs = vec![]; @@ -116,7 +120,7 @@ impl EndCommandOptions { }, ); - activity_store.sync()?; + activity_store.sync().map_err(PaceErrorKind::Storage)?; Ok(UserMessage::new(user_message)) } diff --git a/crates/core/src/commands/hold.rs b/crates/core/src/commands/hold.rs index 10b737ab..508721d9 100644 --- a/crates/core/src/commands/hold.rs +++ b/crates/core/src/commands/hold.rs @@ -14,7 +14,7 @@ use crate::{ config::PaceConfig, domain::intermission::IntermissionAction, error::{PaceResult, UserMessage}, - prelude::ActivityStorage, + prelude::{ActivityStorage, PaceErrorKind}, service::activity_store::ActivityStore, storage::{ActivityStateManagement, SyncStorage}, }; @@ -119,18 +119,21 @@ impl HoldCommandOptions { debug!("Hold options: {hold_opts:?}"); - let activity_store = ActivityStore::with_storage(storage)?; + let activity_store = + ActivityStore::with_storage(storage).map_err(PaceErrorKind::Storage)?; - let user_message = - if let Some(activity) = activity_store.hold_most_recent_active_activity(hold_opts)? { - debug!("Held {}", activity.activity()); + let user_message = if let Some(activity) = activity_store + .hold_most_recent_active_activity(hold_opts) + .map_err(PaceErrorKind::Storage)? + { + debug!("Held {}", activity.activity()); - activity_store.sync()?; + activity_store.sync().map_err(PaceErrorKind::Storage)?; - format!("Held {}", activity.activity()) - } else { - "No unfinished activities to hold.".to_string() - }; + format!("Held {}", activity.activity()) + } else { + "No unfinished activities to hold.".to_string() + }; Ok(UserMessage::new(user_message)) } diff --git a/crates/core/src/commands/now.rs b/crates/core/src/commands/now.rs index e435ae5b..686c75b2 100644 --- a/crates/core/src/commands/now.rs +++ b/crates/core/src/commands/now.rs @@ -8,7 +8,7 @@ use crate::{ config::PaceConfig, domain::{activity::ActivityItem, filter::ActivityFilterKind}, error::{PaceResult, UserMessage}, - prelude::ActivityStorage, + prelude::{ActivityStorage, PaceErrorKind}, service::activity_store::ActivityStore, storage::{ActivityQuerying, ActivityReadOps}, }; @@ -38,28 +38,31 @@ impl NowCommandOptions { config: &PaceConfig, storage: Arc, ) -> PaceResult { - let activity_store = ActivityStore::with_storage(storage)?; + let activity_store = + ActivityStore::with_storage(storage).map_err(PaceErrorKind::Storage)?; - let user_message = (activity_store.list_current_activities(ActivityFilterKind::Active)?) - .map_or_else( - || "No activities are currently running.".to_string(), - |activities| { - debug!("Current Activities: {:?}", activities); + let user_message = (activity_store + .list_current_activities(ActivityFilterKind::Active) + .map_err(PaceErrorKind::Storage)?) + .map_or_else( + || "No activities are currently running.".to_string(), + |activities| { + debug!("Current Activities: {:?}", activities); - // Get the activity items - let activity_items = activities - .iter() - .flat_map(|activity_id| activity_store.read_activity(*activity_id)) - .collect::>(); + // Get the activity items + let activity_items = activities + .iter() + .flat_map(|activity_id| activity_store.read_activity(*activity_id)) + .collect::>(); - let mut msgs = vec![]; - for activity in &activity_items { - msgs.push(format!("{}", activity.activity())); - } + let mut msgs = vec![]; + for activity in &activity_items { + msgs.push(format!("{}", activity.activity())); + } - msgs.join("\n") - }, - ); + msgs.join("\n") + }, + ); Ok(UserMessage::new(user_message)) } diff --git a/crates/core/src/commands/reflect.rs b/crates/core/src/commands/reflect.rs index 363c58ec..f47dce29 100644 --- a/crates/core/src/commands/reflect.rs +++ b/crates/core/src/commands/reflect.rs @@ -15,7 +15,7 @@ use crate::{ config::PaceConfig, domain::{activity::ActivityKind, filter::FilterOptions, reflection::ReflectionsFormatKind}, error::{PaceResult, TemplatingErrorKind, UserMessage}, - prelude::ActivityStorage, + prelude::{ActivityStorage, PaceErrorKind}, service::{activity_store::ActivityStore, activity_tracker::ActivityTracker}, template::{PaceReflectionTemplate, TEMPLATES}, }; @@ -147,14 +147,16 @@ impl ReflectCommandOptions { PaceTimeZoneKind::NotSet, ))?; - let activity_store = ActivityStore::with_storage(storage)?; + let activity_store = + ActivityStore::with_storage(storage).map_err(PaceErrorKind::Storage)?; let activity_tracker = ActivityTracker::with_activity_store(activity_store); debug!("Displaying reflection for time frame: {}", time_frame); - let Some(reflection) = - activity_tracker.generate_reflection(FilterOptions::from(self), time_frame)? + let Some(reflection) = activity_tracker + .generate_reflection(FilterOptions::from(self), time_frame) + .map_err(PaceErrorKind::Storage)? else { return Ok(UserMessage::new( "No activities found for the specified time frame", diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index fa687eac..75a11ec9 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -148,7 +148,6 @@ pub enum ActivityLogStorageKind { #[default] File, Database, - #[cfg(test)] InMemory, } diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 882f308e..00bc7fd7 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -1,6 +1,7 @@ //! Error types and Result module. use displaydoc::Display; +use eyre::Result as EyreResult; use miette::Diagnostic; use pace_time::error::PaceTimeErrorKind; use std::{error::Error, io, path::PathBuf}; @@ -8,6 +9,12 @@ use thiserror::Error; use crate::domain::activity::{Activity, ActivityGuid}; +macro_rules! impl_pace_error_marker { + ($error:ty) => { + impl PaceErrorMarker for $error {} + }; +} + /// Result type that is being returned from test functions and methods that can fail and thus have errors. pub type TestResult = Result>; @@ -17,6 +24,9 @@ pub type PaceResult = Result; /// Result type that is being returned from methods that have optional return values and can fail thus having [`PaceError`]s. pub type PaceOptResult = PaceResult>; +pub type PaceStorageResult = EyreResult; +pub type PaceStorageOptResult = EyreResult>; + /// User message type that is being returned from methods that need to print a message to the user. #[derive(Debug, Clone, PartialEq, Eq)] pub struct UserMessage { @@ -102,9 +112,6 @@ impl PaceError { #[non_exhaustive] #[derive(Error, Debug, Display)] pub enum PaceErrorKind { - // /// [`CommandErrorKind`] describes the errors that can happen while executing a high-level command - // #[error(transparent)] - // Command(#[from] CommandErrorKind), /// [`std::io::Error`] #[error(transparent)] StdIo(#[from] std::io::Error), @@ -153,15 +160,16 @@ pub enum PaceErrorKind { /// Configuration file not found, please run `pace setup config` to initialize `pace` ParentDirNotFound(PathBuf), - /// Database storage not implemented, yet! - DatabaseStorageNotImplemented, - /// There is no path available to store the activity log NoPathAvailable, /// Templating error: {0} #[error(transparent)] Template(#[from] TemplatingErrorKind), + + /// Storage error: {0} + #[error(transparent)] + Storage(#[from] eyre::Report), } /// [`ActivityLogErrorKind`] describes the errors that can happen while dealing with the activity log. @@ -297,20 +305,23 @@ pub enum ActivityStoreErrorKind { /// Missing category for activity: {0} MissingCategoryForActivity(ActivityGuid), + + /// Creating ActivityStore from storage failed + CreatingFromStorageFailed, } trait PaceErrorMarker: Error {} -impl PaceErrorMarker for std::io::Error {} -impl PaceErrorMarker for toml::de::Error {} -impl PaceErrorMarker for toml::ser::Error {} -impl PaceErrorMarker for serde_json::Error {} -impl PaceErrorMarker for chrono::ParseError {} -impl PaceErrorMarker for chrono::OutOfRangeError {} -impl PaceErrorMarker for ActivityLogErrorKind {} -impl PaceErrorMarker for PaceTimeErrorKind {} -impl PaceErrorMarker for ActivityStoreErrorKind {} -impl PaceErrorMarker for TemplatingErrorKind {} +impl_pace_error_marker!(std::io::Error); +impl_pace_error_marker!(toml::de::Error); +impl_pace_error_marker!(toml::ser::Error); +impl_pace_error_marker!(serde_json::Error); +impl_pace_error_marker!(chrono::ParseError); +impl_pace_error_marker!(chrono::OutOfRangeError); +impl_pace_error_marker!(ActivityLogErrorKind); +impl_pace_error_marker!(PaceTimeErrorKind); +impl_pace_error_marker!(ActivityStoreErrorKind); +impl_pace_error_marker!(TemplatingErrorKind); impl From for PaceError where diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index f8c2cd76..468425bc 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -62,8 +62,8 @@ pub mod prelude { status::ActivityStatusKind, }, error::{ - ActivityLogErrorKind, PaceError, PaceErrorKind, PaceOptResult, PaceResult, TestResult, - UserMessage, + ActivityLogErrorKind, PaceError, PaceErrorKind, PaceOptResult, PaceResult, + PaceStorageOptResult, PaceStorageResult, TestResult, UserMessage, }, service::{activity_store::ActivityStore, activity_tracker::ActivityTracker}, storage::{ diff --git a/crates/core/src/service/activity_store.rs b/crates/core/src/service/activity_store.rs index 3918dbf3..61c26d2c 100644 --- a/crates/core/src/service/activity_store.rs +++ b/crates/core/src/service/activity_store.rs @@ -24,7 +24,7 @@ use crate::{ reflection::{SummaryActivityGroup, SummaryGroupByCategory}, status::ActivityStatusKind, }, - error::{ActivityStoreErrorKind, PaceOptResult, PaceResult}, + error::{ActivityStoreErrorKind, PaceStorageOptResult, PaceStorageResult}, storage::{ ActivityQuerying, ActivityReadOps, ActivityStateManagement, ActivityStorage, ActivityWriteOps, SyncStorage, @@ -69,7 +69,7 @@ impl ActivityStore { /// /// This method returns a new `ActivityStore` if the storage backend /// was successfully created - pub fn with_storage(storage: Arc) -> PaceResult { + pub fn with_storage(storage: Arc) -> PaceStorageResult { debug!( "Creating activity store with storage: {}", storage.identify() @@ -98,7 +98,7 @@ impl ActivityStore { /// # Returns /// /// This method returns `Ok(())` if the cache was successfully populated - fn populate_caches(&mut self) -> PaceResult<()> { + fn populate_caches(&mut self) -> PaceStorageResult<()> { self.cache.by_start_date = self .storage .group_activities_by_start_date()? @@ -112,7 +112,7 @@ impl ActivityStore { &self, filter_opts: FilterOptions, time_range_opts: TimeRangeOptions, - ) -> PaceOptResult { + ) -> PaceStorageOptResult { let Some(activity_guids) = self.list_activities_by_time_range(time_range_opts)? else { debug!("No activities found for time range: {:?}", time_range_opts); @@ -213,7 +213,7 @@ impl ActivityStore { impl ActivityStorage for ActivityStore { #[tracing::instrument(skip(self))] - fn setup(&self) -> PaceResult<()> { + fn setup(&self) -> PaceStorageResult<()> { self.storage.setup() } @@ -223,33 +223,36 @@ impl ActivityStorage for ActivityStore { } #[tracing::instrument(skip(self))] - fn teardown(&self) -> PaceResult<()> { + fn teardown(&self) -> PaceStorageResult<()> { self.storage.teardown() } } impl SyncStorage for ActivityStore { #[tracing::instrument(skip(self))] - fn sync(&self) -> PaceResult<()> { + fn sync(&self) -> PaceStorageResult<()> { self.storage.sync() } } impl ActivityReadOps for ActivityStore { #[tracing::instrument(skip(self))] - fn read_activity(&self, activity_id: ActivityGuid) -> PaceResult { + fn read_activity(&self, activity_id: ActivityGuid) -> PaceStorageResult { self.storage.read_activity(activity_id) } #[tracing::instrument(skip(self))] - fn list_activities(&self, filter: ActivityFilterKind) -> PaceOptResult { + fn list_activities( + &self, + filter: ActivityFilterKind, + ) -> PaceStorageOptResult { self.storage.list_activities(filter) } } impl ActivityWriteOps for ActivityStore { #[tracing::instrument(skip(self))] - fn create_activity(&self, activity: Activity) -> PaceResult { + fn create_activity(&self, activity: Activity) -> PaceStorageResult { self.storage.create_activity(activity) } @@ -259,7 +262,7 @@ impl ActivityWriteOps for ActivityStore { activity_id: ActivityGuid, updated_activity: Activity, update_opts: UpdateOptions, - ) -> PaceResult { + ) -> PaceStorageResult { self.storage .update_activity(activity_id, updated_activity, update_opts) } @@ -269,14 +272,14 @@ impl ActivityWriteOps for ActivityStore { &self, activity_id: ActivityGuid, delete_opts: DeleteOptions, - ) -> PaceResult { + ) -> PaceStorageResult { self.storage.delete_activity(activity_id, delete_opts) } } impl ActivityStateManagement for ActivityStore { #[tracing::instrument(skip(self))] - fn begin_activity(&self, activity: Activity) -> PaceResult { + fn begin_activity(&self, activity: Activity) -> PaceStorageResult { self.storage.begin_activity(activity) } @@ -285,17 +288,20 @@ impl ActivityStateManagement for ActivityStore { &self, activity_id: ActivityGuid, end_opts: EndOptions, - ) -> PaceResult { + ) -> PaceStorageResult { self.storage.end_activity(activity_id, end_opts) } #[tracing::instrument(skip(self))] - fn end_all_activities(&self, end_opts: EndOptions) -> PaceOptResult> { + fn end_all_activities(&self, end_opts: EndOptions) -> PaceStorageOptResult> { self.storage.end_all_activities(end_opts) } #[tracing::instrument(skip(self))] - fn end_last_unfinished_activity(&self, end_opts: EndOptions) -> PaceOptResult { + fn end_last_unfinished_activity( + &self, + end_opts: EndOptions, + ) -> PaceStorageOptResult { self.storage.end_last_unfinished_activity(end_opts) } @@ -303,7 +309,7 @@ impl ActivityStateManagement for ActivityStore { fn hold_most_recent_active_activity( &self, hold_opts: HoldOptions, - ) -> PaceOptResult { + ) -> PaceStorageOptResult { self.storage.hold_most_recent_active_activity(hold_opts) } @@ -311,7 +317,7 @@ impl ActivityStateManagement for ActivityStore { fn end_all_active_intermissions( &self, end_opts: EndOptions, - ) -> PaceOptResult> { + ) -> PaceStorageOptResult> { self.storage.end_all_active_intermissions(end_opts) } @@ -320,7 +326,7 @@ impl ActivityStateManagement for ActivityStore { &self, activity_id: ActivityGuid, resume_opts: ResumeOptions, - ) -> PaceResult { + ) -> PaceStorageResult { self.storage.resume_activity(activity_id, resume_opts) } @@ -329,7 +335,7 @@ impl ActivityStateManagement for ActivityStore { &self, activity_id: ActivityGuid, hold_opts: HoldOptions, - ) -> PaceResult { + ) -> PaceStorageResult { self.storage.hold_activity(activity_id, hold_opts) } @@ -337,35 +343,35 @@ impl ActivityStateManagement for ActivityStore { fn resume_most_recent_activity( &self, resume_opts: ResumeOptions, - ) -> PaceOptResult { + ) -> PaceStorageOptResult { self.storage.resume_most_recent_activity(resume_opts) } } impl ActivityQuerying for ActivityStore { #[tracing::instrument(skip(self))] - fn list_activities_by_id(&self) -> PaceOptResult> { + fn list_activities_by_id(&self) -> PaceStorageOptResult> { self.storage.list_activities_by_id() } #[tracing::instrument(skip(self))] fn group_activities_by_duration_range( &self, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { self.storage.group_activities_by_duration_range() } #[tracing::instrument(skip(self))] fn group_activities_by_start_date( &self, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { self.storage.group_activities_by_start_date() } #[tracing::instrument(skip(self))] fn list_activities_with_intermissions( &self, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { self.storage.list_activities_with_intermissions() } @@ -373,12 +379,14 @@ impl ActivityQuerying for ActivityStore { fn group_activities_by_keywords( &self, keyword_opts: KeywordOptions, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { self.storage.group_activities_by_keywords(keyword_opts) } #[tracing::instrument(skip(self))] - fn group_activities_by_kind(&self) -> PaceOptResult>> { + fn group_activities_by_kind( + &self, + ) -> PaceStorageOptResult>> { self.storage.group_activities_by_kind() } @@ -386,14 +394,14 @@ impl ActivityQuerying for ActivityStore { fn list_activities_by_time_range( &self, time_range_opts: TimeRangeOptions, - ) -> PaceOptResult> { + ) -> PaceStorageOptResult> { self.storage.list_activities_by_time_range(time_range_opts) } #[tracing::instrument(skip(self))] fn group_activities_by_status( &self, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { self.storage.group_activities_by_status() } } diff --git a/crates/core/src/service/activity_tracker.rs b/crates/core/src/service/activity_tracker.rs index e96e8c26..c744ff4e 100644 --- a/crates/core/src/service/activity_tracker.rs +++ b/crates/core/src/service/activity_tracker.rs @@ -5,7 +5,7 @@ use tracing::debug; use crate::{ domain::{filter::FilterOptions, reflection::ReflectionSummary}, - error::PaceOptResult, + error::PaceStorageOptResult, service::activity_store::ActivityStore, }; @@ -32,7 +32,7 @@ impl ActivityTracker { &self, filter_opts: FilterOptions, time_frame: PaceTimeFrame, - ) -> PaceOptResult { + ) -> PaceStorageOptResult { let time_range_opts = TimeRangeOptions::try_from(time_frame)?; let Some(summary_groups) = self diff --git a/crates/core/src/storage.rs b/crates/core/src/storage.rs index 731ad9d0..728c4031 100644 --- a/crates/core/src/storage.rs +++ b/crates/core/src/storage.rs @@ -17,7 +17,7 @@ use crate::{ filter::{ActivityFilterKind, FilteredActivities}, status::ActivityStatusKind, }, - error::{PaceOptResult, PaceResult}, + error::{PaceStorageOptResult, PaceStorageResult}, }; impl Debug for dyn ActivityStorage { @@ -39,7 +39,7 @@ pub trait SyncStorage { /// # Returns /// /// If the storage was synced successfully it should return `Ok(())`. - fn sync(&self) -> PaceResult<()>; + fn sync(&self) -> PaceStorageResult<()>; } /// The trait that all storage backends must implement. This allows us to swap out the storage @@ -65,7 +65,7 @@ pub trait ActivityStorage: /// # Errors /// /// This function should return an error if the storage backend cannot be setup. - fn setup(&self) -> PaceResult<()>; + fn setup(&self) -> PaceStorageResult<()>; /// Teardown the storage backend. This is called once when the application stops. /// @@ -78,7 +78,7 @@ pub trait ActivityStorage: /// # Returns /// /// If the storage backend was torn down successfully it should return `Ok(())`. - fn teardown(&self) -> PaceResult<()>; + fn teardown(&self) -> PaceStorageResult<()>; /// Identify the storage backend. /// @@ -109,7 +109,7 @@ pub trait ActivityReadOps { /// # Returns /// /// The activity that was read from the storage backend. If no activity is found, it should return `Ok(None)`. - fn read_activity(&self, activity_id: ActivityGuid) -> PaceResult; + fn read_activity(&self, activity_id: ActivityGuid) -> PaceStorageResult; /// List activities from the storage backend. /// @@ -124,7 +124,10 @@ pub trait ActivityReadOps { /// # Returns /// /// A collection of the activities that were loaded from the storage backend. Returns Ok(None) if no activities are found. - fn list_activities(&self, filter: ActivityFilterKind) -> PaceOptResult; + fn list_activities( + &self, + filter: ActivityFilterKind, + ) -> PaceStorageOptResult; } /// Basic CUD Operations for Activities in the storage backend. @@ -145,7 +148,7 @@ pub trait ActivityWriteOps: ActivityReadOps { /// # Returns /// /// If the activity was created successfully it should return the ID of the created activity. - fn create_activity(&self, activity: Activity) -> PaceResult; + fn create_activity(&self, activity: Activity) -> PaceStorageResult; /// Update an existing activity in the storage backend. /// @@ -175,7 +178,7 @@ pub trait ActivityWriteOps: ActivityReadOps { activity_id: ActivityGuid, updated_activity: Activity, update_opts: UpdateOptions, - ) -> PaceResult; + ) -> PaceStorageResult; /// Delete an activity from the storage backend. /// @@ -194,7 +197,7 @@ pub trait ActivityWriteOps: ActivityReadOps { &self, activity_id: ActivityGuid, delete_opts: DeleteOptions, - ) -> PaceResult; + ) -> PaceStorageResult; } /// Managing Activity State @@ -217,7 +220,7 @@ pub trait ActivityStateManagement: ActivityReadOps + ActivityWriteOps + Activity /// # Returns /// /// If the activity was started successfully it should return the ID of the started activity. - fn begin_activity(&self, mut activity: Activity) -> PaceResult { + fn begin_activity(&self, mut activity: Activity) -> PaceStorageResult { // End all unfinished activities before starting a new one, // we don't want to have multiple activities running at the same time let _ = self.end_all_activities(EndOptions::default())?; @@ -247,7 +250,7 @@ pub trait ActivityStateManagement: ActivityReadOps + ActivityWriteOps + Activity &self, activity_id: ActivityGuid, hold_opts: HoldOptions, - ) -> PaceResult; + ) -> PaceStorageResult; /// Resume an activity in the storage backend. /// @@ -267,7 +270,7 @@ pub trait ActivityStateManagement: ActivityReadOps + ActivityWriteOps + Activity &self, activity_id: ActivityGuid, resume_opts: ResumeOptions, - ) -> PaceResult; + ) -> PaceStorageResult; /// Resume the most recent activity in the storage backend. /// @@ -285,7 +288,7 @@ pub trait ActivityStateManagement: ActivityReadOps + ActivityWriteOps + Activity fn resume_most_recent_activity( &self, resume_opts: ResumeOptions, - ) -> PaceOptResult; + ) -> PaceStorageOptResult; /// End an activity in the storage backend. /// @@ -305,7 +308,7 @@ pub trait ActivityStateManagement: ActivityReadOps + ActivityWriteOps + Activity &self, activity_id: ActivityGuid, end_opts: EndOptions, - ) -> PaceResult; + ) -> PaceStorageResult; /// End all activities in the storage backend. /// @@ -320,7 +323,7 @@ pub trait ActivityStateManagement: ActivityReadOps + ActivityWriteOps + Activity /// # Returns /// /// A collection of the activities that were ended. Returns Ok(None) if no activities were ended. - fn end_all_activities(&self, end_opts: EndOptions) -> PaceOptResult>; + fn end_all_activities(&self, end_opts: EndOptions) -> PaceStorageOptResult>; /// End all active intermissions in the storage backend. /// @@ -338,7 +341,7 @@ pub trait ActivityStateManagement: ActivityReadOps + ActivityWriteOps + Activity fn end_all_active_intermissions( &self, end_opts: EndOptions, - ) -> PaceOptResult>; + ) -> PaceStorageOptResult>; /// End the last unfinished activity in the storage backend. /// @@ -353,7 +356,10 @@ pub trait ActivityStateManagement: ActivityReadOps + ActivityWriteOps + Activity /// # Returns /// /// The activity that was ended. Returns Ok(None) if no activity was ended. - fn end_last_unfinished_activity(&self, end_opts: EndOptions) -> PaceOptResult; + fn end_last_unfinished_activity( + &self, + end_opts: EndOptions, + ) -> PaceStorageOptResult; /// Hold the most recent activity that is active in the storage backend. /// @@ -376,7 +382,7 @@ pub trait ActivityStateManagement: ActivityReadOps + ActivityWriteOps + Activity fn hold_most_recent_active_activity( &self, hold_opts: HoldOptions, - ) -> PaceOptResult; + ) -> PaceStorageOptResult; } /// Querying Activities @@ -405,7 +411,7 @@ pub trait ActivityQuerying: ActivityReadOps { // TODO!: This method requires defining what constitutes short, medium, and long durations. fn group_activities_by_duration_range( &self, - ) -> PaceOptResult>>; + ) -> PaceStorageOptResult>>; /// Group activities by their start date. This can help in analyzing how /// activities are distributed over time. @@ -423,7 +429,7 @@ pub trait ActivityQuerying: ActivityReadOps { // TODO!: for the groupings, so we can distinguish between start and end date groupings. fn group_activities_by_start_date( &self, - ) -> PaceOptResult>>; + ) -> PaceStorageOptResult>>; /// Retrieve activities that have one or more intermissions, useful for identifying /// potential inefficiencies or breaks. @@ -439,7 +445,7 @@ pub trait ActivityQuerying: ActivityReadOps { /// If no activities are found, it should return `Ok(None)`. fn list_activities_with_intermissions( &self, - ) -> PaceOptResult>>; + ) -> PaceStorageOptResult>>; /// Group activities based on keywords, e.g., category, tags, etc. /// @@ -461,7 +467,7 @@ pub trait ActivityQuerying: ActivityReadOps { fn group_activities_by_keywords( &self, keyword_opts: KeywordOptions, - ) -> PaceOptResult>>; + ) -> PaceStorageOptResult>>; /// Group activities based on their kind (e.g., Task, Intermission). /// @@ -474,7 +480,9 @@ pub trait ActivityQuerying: ActivityReadOps { /// A collection of the activities with their kind. /// The key is the kind of the activity, and the value is a list of activities of that kind. /// If no activities are found, it should return `Ok(None)`. - fn group_activities_by_kind(&self) -> PaceOptResult>>; + fn group_activities_by_kind( + &self, + ) -> PaceStorageOptResult>>; /// List activities by time range from the storage backend. /// @@ -493,7 +501,7 @@ pub trait ActivityQuerying: ActivityReadOps { fn list_activities_by_time_range( &self, time_range_opts: TimeRangeOptions, - ) -> PaceOptResult>; + ) -> PaceStorageOptResult>; /// Group activities by their status from the storage backend. /// @@ -507,7 +515,7 @@ pub trait ActivityQuerying: ActivityReadOps { /// If no activities are found, it should return `Ok(None)`. fn group_activities_by_status( &self, - ) -> PaceOptResult>>; + ) -> PaceStorageOptResult>>; /// List all current activities from the storage backend matching an `ActivityFilter`. /// @@ -522,7 +530,7 @@ pub trait ActivityQuerying: ActivityReadOps { fn list_current_activities( &self, filter: ActivityFilterKind, - ) -> PaceOptResult> { + ) -> PaceStorageOptResult> { Ok(self .list_activities(filter)? .map(FilteredActivities::into_vec)) @@ -538,7 +546,7 @@ 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>; + fn list_activities_by_id(&self) -> PaceStorageOptResult>; /// List all active intermissions from the storage backend. /// @@ -550,7 +558,7 @@ pub trait ActivityQuerying: ActivityReadOps { /// /// A collection of the activities that are currently active intermissions. /// If no activities are found, it should return `Ok(None)`. - fn list_active_intermissions(&self) -> PaceOptResult> { + fn list_active_intermissions(&self) -> PaceStorageOptResult> { Ok(self .list_activities(ActivityFilterKind::ActiveIntermission)? .map(FilteredActivities::into_vec)) @@ -570,7 +578,7 @@ pub trait ActivityQuerying: ActivityReadOps { /// /// A collection of the most recent activities. /// If no activities are found, it should return `Ok(None)`. - fn list_most_recent_activities(&self, count: usize) -> PaceOptResult> { + fn list_most_recent_activities(&self, count: usize) -> PaceStorageOptResult> { let filtered = self .list_activities(ActivityFilterKind::OnlyActivities)? .map(FilteredActivities::into_vec); @@ -616,7 +624,7 @@ pub trait ActivityQuerying: ActivityReadOps { /// # Returns /// /// If the activity is active, it should return `Ok(true)`. If it is not active, it should return `Ok(false)`. - fn is_activity_active(&self, activity_id: ActivityGuid) -> PaceResult { + fn is_activity_active(&self, activity_id: ActivityGuid) -> PaceStorageResult { let activity = self.read_activity(activity_id)?; debug!( @@ -649,7 +657,7 @@ pub trait ActivityQuerying: ActivityReadOps { fn list_intermissions_for_activity_id( &self, activity_id: ActivityGuid, - ) -> PaceOptResult> { + ) -> PaceStorageOptResult> { let Some(filtered) = self .list_activities(ActivityFilterKind::Intermission)? .map(FilteredActivities::into_vec) @@ -703,7 +711,7 @@ pub trait ActivityQuerying: ActivityReadOps { fn list_active_intermissions_for_activity_id( &self, activity_id: ActivityGuid, - ) -> PaceOptResult> { + ) -> PaceStorageOptResult> { let guids = self.list_active_intermissions()?.map(|log| { log.iter() .filter_map(|active_intermission_id| { @@ -742,7 +750,7 @@ pub trait ActivityQuerying: ActivityReadOps { /// /// The latest active activity. /// If no activity is found, it should return `Ok(None)`. - fn most_recent_active_activity(&self) -> PaceOptResult { + fn most_recent_active_activity(&self) -> PaceStorageOptResult { let Some(current) = self.list_current_activities(ActivityFilterKind::Active)? else { debug!("No active activities found"); @@ -777,7 +785,7 @@ pub trait ActivityQuerying: ActivityReadOps { /// /// The latest held activity. /// If no activity is found, it should return `Ok(None)`. - fn most_recent_held_activity(&self) -> PaceOptResult { + fn most_recent_held_activity(&self) -> PaceStorageOptResult { let Some(current) = self.list_current_activities(ActivityFilterKind::Held)? else { debug!("No held activities found"); @@ -823,7 +831,7 @@ pub trait ActivityQuerying: ActivityReadOps { // /// # Returns // /// // /// If the tag was added successfully it should return `Ok(())`. -// fn add_tag_to_activity(&self, activity_id: ActivityGuid, tag: &str) -> PaceResult<()>; +// fn add_tag_to_activity(&self, activity_id: ActivityGuid, tag: &str) -> PaceStorageResult<()>; // /// Remove a tag from an activity. // /// @@ -839,7 +847,7 @@ pub trait ActivityQuerying: ActivityReadOps { // /// # Returns // /// // /// If the tag was removed successfully it should return `Ok(())`. -// fn remove_tag_from_activity(&self, activity_id: ActivityGuid, tag: &str) -> PaceResult<()>; +// fn remove_tag_from_activity(&self, activity_id: ActivityGuid, tag: &str) -> PaceStorageResult<()>; // } // /// Archiving Activities @@ -863,7 +871,7 @@ pub trait ActivityQuerying: ActivityReadOps { // /// # Returns // /// // /// If the activity was archived successfully it should return `Ok(())`. -// fn archive_activity(&self, activity_id: ActivityGuid) -> PaceResult<()>; +// fn archive_activity(&self, activity_id: ActivityGuid) -> PaceStorageResult<()>; // /// Unarchive an activity. // /// @@ -878,7 +886,7 @@ pub trait ActivityQuerying: ActivityReadOps { // /// # Returns // /// // /// If the activity was unarchived successfully it should return `Ok(())`. -// fn unarchive_activity(&self, activity_id: ActivityGuid) -> PaceResult<()>; +// fn unarchive_activity(&self, activity_id: ActivityGuid) -> PaceStorageResult<()>; // } // /// Generate Statistics for Activities @@ -902,7 +910,7 @@ pub trait ActivityQuerying: ActivityReadOps { // /// # Returns // /// // /// A summary or statistics of activities within the specified time frame. -// fn generate_activity_statistics(&self, time_frame: TimeFrame) -> PaceResult; +// fn generate_activity_statistics(&self, time_frame: TimeFrame) -> PaceStorageResult; // } // /// Reviewing Activities @@ -931,5 +939,5 @@ pub trait ActivityQuerying: ActivityReadOps { // &self, // start: PaceDateTime, // end: PaceDateTime, -// ) -> PaceResult; +// ) -> PaceStorageResult; // } diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index 77032ae0..25e52c0e 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -26,6 +26,7 @@ rusqlite = ["dep:rusqlite", "dep:libsqlite3-sys"] [dependencies] displaydoc = { workspace = true } +eyre = { workspace = true } itertools = { workspace = true } libsqlite3-sys = { workspace = true, features = ["bundled"], optional = true } merge = { workspace = true } diff --git a/crates/storage/src/error.rs b/crates/storage/src/error.rs index e39b18bb..77da8f13 100644 --- a/crates/storage/src/error.rs +++ b/crates/storage/src/error.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use displaydoc::Display; -use pace_core::prelude::{Activity, ActivityGuid, DatabaseEngineKind}; +use pace_core::prelude::{Activity, ActivityGuid, DatabaseEngineKind, PaceError}; use thiserror::Error; pub type PaceStorageResult = Result; @@ -10,11 +10,14 @@ pub type PaceStorageResult = Result; #[non_exhaustive] #[derive(Error, Debug, Display)] pub enum PaceStorageErrorKind { - /// SQLite error: {0} + /// Pace error: {0} #[error(transparent)] - #[cfg(feature = "rusqlite")] - SQLite(#[from] rusqlite::Error), + PaceError(#[from] PaceError), + // /// SQLite error: {0} + // #[error(transparent)] + // #[cfg(feature = "rusqlite")] + // SQLite(#[from] rusqlite::Error), /// Database error: {0} #[error(transparent)] Database(#[from] DatabaseStorageErrorKind), @@ -25,6 +28,9 @@ pub enum PaceStorageErrorKind { /// Database storage not configured DatabaseStorageNotConfigured, + + /// Database storage not implemented, yet! + StorageNotImplemented, } /// [`DatabaseErrorKind`] describes the errors that can happen while dealing with the SQLite database. diff --git a/crates/storage/src/file.rs b/crates/storage/src/file.rs index 0b677305..623f4768 100644 --- a/crates/storage/src/file.rs +++ b/crates/storage/src/file.rs @@ -11,7 +11,8 @@ use pace_core::prelude::{ Activity, ActivityFilterKind, ActivityGuid, ActivityItem, ActivityKind, ActivityLog, ActivityQuerying, ActivityReadOps, ActivityStateManagement, ActivityStatusKind, ActivityStorage, ActivityWriteOps, DeleteOptions, EndOptions, FilteredActivities, HoldOptions, - KeywordOptions, PaceOptResult, PaceResult, ResumeOptions, SyncStorage, UpdateOptions, + KeywordOptions, PaceStorageOptResult, PaceStorageResult, ResumeOptions, SyncStorage, + UpdateOptions, }; use crate::{error::TomlFileStorageErrorKind, in_memory::InMemoryActivityStorage}; @@ -28,7 +29,7 @@ pub struct TomlActivityStorage { } impl SyncStorage for TomlActivityStorage { - fn sync(&self) -> PaceResult<()> { + fn sync(&self) -> PaceStorageResult<()> { self.sync_to_file() } } @@ -47,7 +48,7 @@ impl TomlActivityStorage { /// # Returns /// /// Returns a new `TomlActivityStorage` - pub fn new(path: impl AsRef) -> PaceResult { + pub fn new(path: impl AsRef) -> PaceStorageResult { let mut storage = Self { cache: InMemoryActivityStorage::new(), path: path.as_ref().to_path_buf(), @@ -70,7 +71,7 @@ impl TomlActivityStorage { /// /// Returns `Ok(())` if the data is loaded successfully #[tracing::instrument(skip(self))] - fn load(&mut self) -> PaceResult<()> { + fn load(&mut self) -> PaceStorageResult<()> { let data = std::fs::read_to_string(&self.path)?; self.cache = InMemoryActivityStorage::from(toml::from_str::(&data)?); @@ -89,7 +90,7 @@ impl TomlActivityStorage { /// /// Returns `Ok(())` if the cache is written successfully #[tracing::instrument(skip(self))] - pub fn sync_to_file(&self) -> PaceResult<()> { + pub fn sync_to_file(&self) -> PaceStorageResult<()> { let data = toml::to_string(&self.cache.get_activity_log())?; write(&self.path, data)?; Ok(()) @@ -98,7 +99,7 @@ impl TomlActivityStorage { impl ActivityStorage for TomlActivityStorage { #[tracing::instrument(skip(self))] - fn setup(&self) -> PaceResult<()> { + fn setup(&self) -> PaceStorageResult<()> { if !self.path.exists() { create_dir_all(self.path.parent().ok_or( TomlFileStorageErrorKind::ParentDirNotFound(self.path.clone()), @@ -116,7 +117,7 @@ impl ActivityStorage for TomlActivityStorage { } #[tracing::instrument(skip(self))] - fn teardown(&self) -> PaceResult<()> { + fn teardown(&self) -> PaceStorageResult<()> { self.sync_to_file() } @@ -128,24 +129,30 @@ impl ActivityStorage for TomlActivityStorage { impl ActivityReadOps for TomlActivityStorage { #[tracing::instrument(skip(self))] - fn read_activity(&self, activity_id: ActivityGuid) -> PaceResult { + fn read_activity(&self, activity_id: ActivityGuid) -> PaceStorageResult { self.cache.read_activity(activity_id) } #[tracing::instrument(skip(self))] - fn list_activities(&self, filter: ActivityFilterKind) -> PaceOptResult { + fn list_activities( + &self, + filter: ActivityFilterKind, + ) -> PaceStorageOptResult { self.cache.list_activities(filter) } } impl ActivityStateManagement for TomlActivityStorage { #[tracing::instrument(skip(self))] - fn end_all_activities(&self, end_opts: EndOptions) -> PaceOptResult> { + fn end_all_activities(&self, end_opts: EndOptions) -> PaceStorageOptResult> { self.cache.end_all_activities(end_opts) } #[tracing::instrument(skip(self))] - fn end_last_unfinished_activity(&self, end_opts: EndOptions) -> PaceOptResult { + fn end_last_unfinished_activity( + &self, + end_opts: EndOptions, + ) -> PaceStorageOptResult { self.cache.end_last_unfinished_activity(end_opts) } @@ -154,7 +161,7 @@ impl ActivityStateManagement for TomlActivityStorage { &self, activity_id: ActivityGuid, end_opts: EndOptions, - ) -> PaceResult { + ) -> PaceStorageResult { self.cache.end_activity(activity_id, end_opts) } @@ -162,7 +169,7 @@ impl ActivityStateManagement for TomlActivityStorage { fn hold_most_recent_active_activity( &self, hold_opts: HoldOptions, - ) -> PaceOptResult { + ) -> PaceStorageOptResult { self.cache.hold_most_recent_active_activity(hold_opts) } @@ -170,7 +177,7 @@ impl ActivityStateManagement for TomlActivityStorage { fn end_all_active_intermissions( &self, end_opts: EndOptions, - ) -> PaceOptResult> { + ) -> PaceStorageOptResult> { self.cache.end_all_active_intermissions(end_opts) } @@ -179,7 +186,7 @@ impl ActivityStateManagement for TomlActivityStorage { &self, activity_id: ActivityGuid, resume_opts: ResumeOptions, - ) -> PaceResult { + ) -> PaceStorageResult { self.cache.resume_activity(activity_id, resume_opts) } @@ -188,7 +195,7 @@ impl ActivityStateManagement for TomlActivityStorage { &self, activity_id: ActivityGuid, hold_opts: HoldOptions, - ) -> PaceResult { + ) -> PaceStorageResult { self.cache.hold_activity(activity_id, hold_opts) } @@ -196,14 +203,14 @@ impl ActivityStateManagement for TomlActivityStorage { fn resume_most_recent_activity( &self, resume_opts: ResumeOptions, - ) -> PaceOptResult { + ) -> PaceStorageOptResult { self.cache.resume_most_recent_activity(resume_opts) } } impl ActivityWriteOps for TomlActivityStorage { #[tracing::instrument(skip(self))] - fn create_activity(&self, activity: Activity) -> PaceResult { + fn create_activity(&self, activity: Activity) -> PaceStorageResult { self.cache.create_activity(activity) } @@ -213,7 +220,7 @@ impl ActivityWriteOps for TomlActivityStorage { activity_id: ActivityGuid, updated_activity: Activity, update_opts: UpdateOptions, - ) -> PaceResult { + ) -> PaceStorageResult { self.cache .update_activity(activity_id, updated_activity, update_opts) } @@ -223,40 +230,40 @@ impl ActivityWriteOps for TomlActivityStorage { &self, activity_id: ActivityGuid, delete_opts: DeleteOptions, - ) -> PaceResult { + ) -> PaceStorageResult { self.cache.delete_activity(activity_id, delete_opts) } } impl ActivityQuerying for TomlActivityStorage { #[tracing::instrument(skip(self))] - fn list_activities_by_id(&self) -> PaceOptResult> { + fn list_activities_by_id(&self) -> PaceStorageOptResult> { self.cache.list_activities_by_id() } #[tracing::instrument(skip(self))] - fn most_recent_active_activity(&self) -> PaceOptResult { + fn most_recent_active_activity(&self) -> PaceStorageOptResult { self.cache.most_recent_active_activity() } #[tracing::instrument(skip(self))] fn group_activities_by_duration_range( &self, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { self.cache.group_activities_by_duration_range() } #[tracing::instrument(skip(self))] fn group_activities_by_start_date( &self, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { self.cache.group_activities_by_start_date() } #[tracing::instrument(skip(self))] fn list_activities_with_intermissions( &self, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { self.cache.list_activities_with_intermissions() } @@ -264,12 +271,14 @@ impl ActivityQuerying for TomlActivityStorage { fn group_activities_by_keywords( &self, keyword_opts: KeywordOptions, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { self.cache.group_activities_by_keywords(keyword_opts) } #[tracing::instrument(skip(self))] - fn group_activities_by_kind(&self) -> PaceOptResult>> { + fn group_activities_by_kind( + &self, + ) -> PaceStorageOptResult>> { self.cache.group_activities_by_kind() } @@ -277,14 +286,14 @@ impl ActivityQuerying for TomlActivityStorage { fn list_activities_by_time_range( &self, time_range_opts: TimeRangeOptions, - ) -> PaceOptResult> { + ) -> PaceStorageOptResult> { self.cache.list_activities_by_time_range(time_range_opts) } #[tracing::instrument(skip(self))] fn group_activities_by_status( &self, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { self.cache.group_activities_by_status() } } diff --git a/crates/storage/src/in_memory.rs b/crates/storage/src/in_memory.rs index 5b4cafde..fba86101 100644 --- a/crates/storage/src/in_memory.rs +++ b/crates/storage/src/in_memory.rs @@ -15,8 +15,8 @@ use pace_core::prelude::{ Activity, ActivityEndOptions, ActivityFilterKind, ActivityGuid, ActivityItem, ActivityKind, ActivityKindOptions, ActivityLog, ActivityLogErrorKind, ActivityQuerying, ActivityReadOps, ActivityStateManagement, ActivityStatusKind, ActivityStorage, ActivityWriteOps, DeleteOptions, - EndOptions, FilteredActivities, HoldOptions, KeywordOptions, PaceOptResult, PaceResult, - ResumeOptions, SyncStorage, UpdateOptions, + EndOptions, FilteredActivities, HoldOptions, KeywordOptions, PaceStorageOptResult, + PaceStorageResult, ResumeOptions, SyncStorage, UpdateOptions, }; /// Type for shared `ActivityLog` @@ -76,12 +76,12 @@ impl Default for InMemoryActivityStorage { } impl ActivityStorage for InMemoryActivityStorage { - fn setup(&self) -> PaceResult<()> { + fn setup(&self) -> PaceStorageResult<()> { debug!("Setting up in-memory storage"); Ok(()) } - fn teardown(&self) -> PaceResult<()> { + fn teardown(&self) -> PaceStorageResult<()> { debug!("Tearing down in-memory storage"); Ok(()) } @@ -92,7 +92,7 @@ impl ActivityStorage for InMemoryActivityStorage { } impl SyncStorage for InMemoryActivityStorage { - fn sync(&self) -> PaceResult<()> { + fn sync(&self) -> PaceStorageResult<()> { debug!("Syncing in-memory storage"); Ok(()) @@ -101,7 +101,7 @@ impl SyncStorage for InMemoryActivityStorage { impl ActivityReadOps for InMemoryActivityStorage { #[tracing::instrument(skip(self))] - fn read_activity(&self, activity_id: ActivityGuid) -> PaceResult { + fn read_activity(&self, activity_id: ActivityGuid) -> PaceStorageResult { let activities = self.log.read(); let activity = activities @@ -117,7 +117,10 @@ impl ActivityReadOps for InMemoryActivityStorage { } #[tracing::instrument(skip(self))] - fn list_activities(&self, filter: ActivityFilterKind) -> PaceOptResult { + fn list_activities( + &self, + filter: ActivityFilterKind, + ) -> PaceStorageOptResult { let activity_log = self.log.read(); let filtered = activity_log @@ -170,7 +173,7 @@ impl ActivityReadOps for InMemoryActivityStorage { impl ActivityWriteOps for InMemoryActivityStorage { #[tracing::instrument(skip(self))] - fn create_activity(&self, activity: Activity) -> PaceResult { + fn create_activity(&self, activity: Activity) -> PaceStorageResult { let activities = self.log.read(); let activity_item = ActivityItem::from(activity); @@ -208,7 +211,7 @@ impl ActivityWriteOps for InMemoryActivityStorage { activity_id: ActivityGuid, updated_activity: Activity, update_opts: UpdateOptions, - ) -> PaceResult { + ) -> PaceStorageResult { let activities = self.log.read(); let original_activity = activities @@ -237,7 +240,7 @@ impl ActivityWriteOps for InMemoryActivityStorage { &self, activity_id: ActivityGuid, delete_opts: DeleteOptions, - ) -> PaceResult { + ) -> PaceStorageResult { let mut activities = self.log.write(); let activity = activities @@ -256,7 +259,7 @@ impl ActivityStateManagement for InMemoryActivityStorage { &self, activity_id: ActivityGuid, end_opts: EndOptions, - ) -> PaceResult { + ) -> PaceStorageResult { let activities = self.log.read(); let begin_time = *activities @@ -285,7 +288,10 @@ impl ActivityStateManagement for InMemoryActivityStorage { } #[tracing::instrument(skip(self))] - fn end_last_unfinished_activity(&self, end_opts: EndOptions) -> PaceOptResult { + fn end_last_unfinished_activity( + &self, + end_opts: EndOptions, + ) -> PaceStorageOptResult { let Some(most_recent) = self.most_recent_active_activity()? else { debug!("No active activity found."); return Ok(None); @@ -299,7 +305,7 @@ impl ActivityStateManagement for InMemoryActivityStorage { } #[tracing::instrument(skip(self))] - fn end_all_activities(&self, end_opts: EndOptions) -> PaceOptResult> { + fn end_all_activities(&self, end_opts: EndOptions) -> PaceStorageOptResult> { let activities = self.log.read(); let endable_activities = activities @@ -325,10 +331,10 @@ impl ActivityStateManagement for InMemoryActivityStorage { let ended_activities = endable_activities .par_iter() - .map(|activity_id| -> PaceResult { + .map(|activity_id| -> PaceStorageResult { self.end_activity(*activity_id, end_opts.clone()) }) - .collect::>>()?; + .collect::>>()?; debug!("Ended activities: {:?}", ended_activities); @@ -346,7 +352,7 @@ impl ActivityStateManagement for InMemoryActivityStorage { fn hold_most_recent_active_activity( &self, hold_opts: HoldOptions, - ) -> PaceOptResult { + ) -> PaceStorageOptResult { // Get id from last activity that is not ended let Some(active_activity) = self.most_recent_active_activity()? else { debug!("No active activity found."); @@ -362,7 +368,7 @@ impl ActivityStateManagement for InMemoryActivityStorage { fn end_all_active_intermissions( &self, end_opts: EndOptions, - ) -> PaceOptResult> { + ) -> PaceStorageOptResult> { let Some(active_intermissions) = self.list_active_intermissions()? else { debug!("No active intermissions found."); @@ -372,11 +378,11 @@ impl ActivityStateManagement for InMemoryActivityStorage { let ended_intermissions = active_intermissions .par_iter() - .map(|activity_id| -> PaceResult { + .map(|activity_id| -> PaceStorageResult { let _ = self.end_activity(*activity_id, end_opts.clone())?; Ok(*activity_id) }) - .collect::>>()?; + .collect::>>()?; debug!("Ended intermissions: {:?}", ended_intermissions); @@ -395,7 +401,7 @@ impl ActivityStateManagement for InMemoryActivityStorage { &self, activity_id: ActivityGuid, resume_opts: ResumeOptions, - ) -> PaceResult { + ) -> PaceStorageResult { let resumable_activity = self.read_activity(activity_id)?; debug!("Resumable activity: {:?}", resumable_activity); @@ -446,7 +452,7 @@ impl ActivityStateManagement for InMemoryActivityStorage { &self, activity_id: ActivityGuid, hold_opts: HoldOptions, - ) -> PaceResult { + ) -> PaceStorageResult { // Get ActivityItem for activity that let active_activity = self.read_activity(activity_id)?; @@ -541,7 +547,7 @@ impl ActivityStateManagement for InMemoryActivityStorage { fn resume_most_recent_activity( &self, resume_opts: ResumeOptions, - ) -> PaceOptResult { + ) -> PaceStorageOptResult { // Get id from last activity that is not ended let Some(active_activity) = self.most_recent_held_activity()? else { debug!("No held activity found."); @@ -562,7 +568,7 @@ impl ActivityStateManagement for InMemoryActivityStorage { impl ActivityQuerying for InMemoryActivityStorage { #[tracing::instrument(skip(self))] - fn list_activities_by_id(&self) -> PaceOptResult> { + fn list_activities_by_id(&self) -> PaceStorageOptResult> { let activities = self.log.read(); let activities_by_id = activities.activities().clone(); @@ -583,14 +589,14 @@ impl ActivityQuerying for InMemoryActivityStorage { #[tracing::instrument(skip(self))] fn group_activities_by_duration_range( &self, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { todo!("Implement grouping activities by duration range") } #[tracing::instrument(skip(self))] fn group_activities_by_start_date( &self, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { let activities = self.log.read(); Some(activities.activities().iter().try_fold( @@ -613,7 +619,7 @@ impl ActivityQuerying for InMemoryActivityStorage { #[tracing::instrument(skip(self))] fn list_activities_with_intermissions( &self, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { let Some(intermissions) = self .list_activities(ActivityFilterKind::Intermission)? .map(FilteredActivities::into_vec) @@ -660,7 +666,7 @@ impl ActivityQuerying for InMemoryActivityStorage { fn group_activities_by_keywords( &self, keyword_opts: KeywordOptions, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { let activities = self.log.read(); Some(activities.activities().iter().try_fold( @@ -706,7 +712,9 @@ impl ActivityQuerying for InMemoryActivityStorage { } #[tracing::instrument(skip(self))] - fn group_activities_by_kind(&self) -> PaceOptResult>> { + fn group_activities_by_kind( + &self, + ) -> PaceStorageOptResult>> { let activities = self.log.read(); Some(activities.activities().iter().try_fold( @@ -732,7 +740,7 @@ impl ActivityQuerying for InMemoryActivityStorage { #[tracing::instrument(skip(self))] fn group_activities_by_status( &self, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { let activities = self.log.read(); Some(activities.activities().iter().try_fold( @@ -759,7 +767,7 @@ impl ActivityQuerying for InMemoryActivityStorage { fn list_activities_by_time_range( &self, time_range_opts: TimeRangeOptions, - ) -> PaceOptResult> { + ) -> PaceStorageOptResult> { let Some(filtered_activities) = self .list_activities(ActivityFilterKind::TimeRange(time_range_opts))? .map(FilteredActivities::into_vec) diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index af19872d..552b3a96 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -1,10 +1,12 @@ use std::sync::Arc; -use pace_core::prelude::{ActivityLogStorageKind, ActivityStorage, DatabaseEngineKind, PaceConfig}; +use pace_core::prelude::{ + ActivityLogStorageKind, ActivityStorage, DatabaseEngineKind, PaceConfig, PaceStorageResult, +}; use tracing::debug; use crate::{ - error::{DatabaseStorageErrorKind, PaceStorageErrorKind, PaceStorageResult}, + error::{DatabaseStorageErrorKind, PaceStorageErrorKind}, file::TomlActivityStorage, in_memory::InMemoryActivityStorage, sqlite::SqliteActivityStorage, @@ -21,6 +23,8 @@ pub mod in_memory; #[cfg(feature = "rusqlite")] pub mod sqlite; +pub mod storage_types; + /// Get the storage backend from the configuration. /// /// # Arguments @@ -35,48 +39,53 @@ pub mod sqlite; /// /// The storage backend. pub fn get_storage_from_config(config: &PaceConfig) -> PaceStorageResult> { - let storage: dyn ActivityStorage = match config.general().activity_log_options().storage_kind() - { - ActivityLogStorageKind::File => { - TomlActivityStorage::new(config.general().activity_log_options().path())?.into() - } - ActivityLogStorageKind::Database => { - if config.database().is_some() { - let Some(db_config) = config.database() else { - return Err(DatabaseStorageErrorKind::NoConfigSettings.into()); - }; - - match db_config.engine() { - DatabaseEngineKind::Sqlite => { - #[cfg(feature = "rusqlite")] - { - let connection_string = config - .database() - .as_ref() - .ok_or(DatabaseStorageErrorKind::NoConnectionString)? - .connection_string(); - - debug!("Connecting to database: {}", &connection_string); - - SqliteActivityStorage::new(connection_string.clone())?.into() + let storage: Arc = + match config.general().activity_log_options().storage_kind() { + ActivityLogStorageKind::File => Arc::new(TomlActivityStorage::new( + config.general().activity_log_options().path(), + )?), + ActivityLogStorageKind::Database => { + if config.database().is_some() { + let Some(db_config) = config.database() else { + return Err(DatabaseStorageErrorKind::NoConfigSettings.into()); + }; + + let storage: Arc = match db_config.engine() { + DatabaseEngineKind::Sqlite => { + #[cfg(feature = "rusqlite")] + { + let connection_string = config + .database() + .as_ref() + .ok_or(DatabaseStorageErrorKind::NoConnectionString)? + .connection_string(); + + debug!("Connecting to database: {}", &connection_string); + + Arc::new(SqliteActivityStorage::new(connection_string.clone())?) + } + + #[cfg(not(feature = "rusqlite"))] + return Err(PaceErrorKind::DatabaseStorageNotImplemented.into()); + } + engine => { + return Err(DatabaseStorageErrorKind::UnsupportedDatabaseEngine( + *engine, + ) + .into()) } - #[cfg(not(feature = "rusqlite"))] - return Err(PaceErrorKind::DatabaseStorageNotImplemented.into()); - } - engine => { - return Err( - DatabaseStorageErrorKind::UnsupportedDatabaseEngine(*engine).into() - ) - } + }; + + storage } - } - return Err(PaceStorageErrorKind::DatabaseStorageNotConfigured.into()); - } - ActivityLogStorageKind::InMemory => InMemoryActivityStorage::new().into(), - }; + return Err(PaceStorageErrorKind::DatabaseStorageNotConfigured.into()); + } + ActivityLogStorageKind::InMemory => Arc::new(InMemoryActivityStorage::new()), + _ => return Err(PaceStorageErrorKind::StorageNotImplemented.into()), + }; - debug!("Using storage backend: {}", storage); + debug!("Using storage backend: {:?}", storage); - Ok(Arc::new(storage)) + Ok(storage) } diff --git a/crates/storage/src/sqlite.rs b/crates/storage/src/sqlite.rs index 5805351f..dbb9ade7 100644 --- a/crates/storage/src/sqlite.rs +++ b/crates/storage/src/sqlite.rs @@ -10,10 +10,10 @@ use pace_core::prelude::{ Activity, ActivityFilterKind, ActivityGuid, ActivityItem, ActivityKind, ActivityQuerying, ActivityReadOps, ActivityStateManagement, ActivityStatusKind, ActivityStorage, ActivityWriteOps, DeleteOptions, EndOptions, FilteredActivities, HoldOptions, KeywordOptions, - PaceOptResult, PaceResult, ResumeOptions, SyncStorage, UpdateOptions, + PaceStorageOptResult, PaceStorageResult, ResumeOptions, SyncStorage, UpdateOptions, }; -use crate::error::{DatabaseStorageErrorKind, PaceStorageResult}; +use crate::error::DatabaseStorageErrorKind; pub trait FromRow { fn from_row(row: &rusqlite::Row<'_>) -> PaceStorageResult @@ -37,7 +37,7 @@ impl SqliteActivityStorage { } impl ActivityStorage for SqliteActivityStorage { - fn setup(&self) -> PaceResult<()> { + fn setup(&self) -> PaceStorageResult<()> { // we embed `db/schema.sql` and run it against the database // as a setup step let schema = include_str!("../../../db/schema.sql"); @@ -47,7 +47,7 @@ impl ActivityStorage for SqliteActivityStorage { Ok(()) } - fn teardown(&self) -> PaceResult<()> { + fn teardown(&self) -> PaceStorageResult<()> { Ok(()) } @@ -57,7 +57,7 @@ impl ActivityStorage for SqliteActivityStorage { } impl SyncStorage for SqliteActivityStorage { - fn sync(&self) -> PaceResult<()> { + fn sync(&self) -> PaceStorageResult<()> { // We sync activities to the database in each operation // so we don't need to do anything here @@ -67,7 +67,7 @@ impl SyncStorage for SqliteActivityStorage { impl ActivityReadOps for SqliteActivityStorage { #[tracing::instrument] - fn read_activity(&self, activity_id: ActivityGuid) -> PaceResult { + fn read_activity(&self, activity_id: ActivityGuid) -> PaceStorageResult { let mut stmt = self .connection .prepare("SELECT * FROM activities WHERE id = ?1")?; @@ -87,7 +87,10 @@ impl ActivityReadOps for SqliteActivityStorage { } #[tracing::instrument] - fn list_activities(&self, filter: ActivityFilterKind) -> PaceOptResult { + fn list_activities( + &self, + filter: ActivityFilterKind, + ) -> PaceStorageOptResult { let mut stmt = self.connection.prepare(filter.to_sql_statement())?; let activity_item_iter = stmt.query_map([], |row| Ok(ActivityGuid::from_row(&row)))?; @@ -121,7 +124,7 @@ impl ActivityReadOps for SqliteActivityStorage { } impl ActivityWriteOps for SqliteActivityStorage { - fn create_activity(&self, activity: Activity) -> PaceResult { + fn create_activity(&self, activity: Activity) -> PaceStorageResult { let tx = self.connection.transaction()?; let mut stmt = tx.prepare(activity.to_sql_prepare_statement())?; @@ -141,7 +144,7 @@ impl ActivityWriteOps for SqliteActivityStorage { activity_id: ActivityGuid, updated_activity: Activity, update_opts: UpdateOptions, - ) -> PaceResult { + ) -> PaceStorageResult { todo!() } @@ -149,7 +152,7 @@ impl ActivityWriteOps for SqliteActivityStorage { &self, activity_id: ActivityGuid, delete_opts: DeleteOptions, - ) -> PaceResult { + ) -> PaceStorageResult { let activity = self.read_activity(activity_id)?; let tx = self.connection.transaction()?; @@ -168,7 +171,7 @@ impl ActivityStateManagement for SqliteActivityStorage { &self, activity_id: ActivityGuid, hold_opts: HoldOptions, - ) -> PaceResult { + ) -> PaceStorageResult { todo!() } @@ -176,14 +179,14 @@ impl ActivityStateManagement for SqliteActivityStorage { &self, activity_id: ActivityGuid, resume_opts: ResumeOptions, - ) -> PaceResult { + ) -> PaceStorageResult { todo!() } fn resume_most_recent_activity( &self, resume_opts: ResumeOptions, - ) -> PaceOptResult { + ) -> PaceStorageOptResult { todo!() } @@ -191,282 +194,81 @@ impl ActivityStateManagement for SqliteActivityStorage { &self, activity_id: ActivityGuid, end_opts: EndOptions, - ) -> PaceResult { + ) -> PaceStorageResult { todo!() } - fn end_all_activities(&self, end_opts: EndOptions) -> PaceOptResult> { + fn end_all_activities(&self, end_opts: EndOptions) -> PaceStorageOptResult> { todo!() } fn end_all_active_intermissions( &self, end_opts: EndOptions, - ) -> PaceOptResult> { + ) -> PaceStorageOptResult> { todo!() } - fn end_last_unfinished_activity(&self, end_opts: EndOptions) -> PaceOptResult { + fn end_last_unfinished_activity( + &self, + end_opts: EndOptions, + ) -> PaceStorageOptResult { todo!() } fn hold_most_recent_active_activity( &self, hold_opts: HoldOptions, - ) -> PaceOptResult { + ) -> PaceStorageOptResult { todo!() } } impl ActivityQuerying for SqliteActivityStorage { fn group_activities_by_duration_range( &self, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { todo!() } fn group_activities_by_start_date( &self, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { todo!() } fn list_activities_with_intermissions( &self, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { todo!() } fn group_activities_by_keywords( &self, keyword_opts: KeywordOptions, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { todo!() } - fn group_activities_by_kind(&self) -> PaceOptResult>> { + fn group_activities_by_kind( + &self, + ) -> PaceStorageOptResult>> { todo!() } fn list_activities_by_time_range( &self, time_range_opts: TimeRangeOptions, - ) -> PaceOptResult> { + ) -> PaceStorageOptResult> { todo!() } fn group_activities_by_status( &self, - ) -> PaceOptResult>> { + ) -> PaceStorageOptResult>> { todo!() } - fn list_activities_by_id(&self) -> PaceOptResult> { + fn list_activities_by_id(&self) -> PaceStorageOptResult> { todo!() } } - -pub mod sql_conversion { - use std::{collections::HashSet, str::FromStr}; - - use pace_time::date_time::PaceDateTime; - use rusqlite::{types::FromSql, Row, ToSql}; - use ulid::Ulid; - - // use pace_time::rusqlite::*; - - use pace_core::prelude::{ - Activity, ActivityEndOptions, ActivityFilterKind, ActivityGuid, ActivityItem, ActivityKind, - ActivityKindOptions, ActivityStatusKind, PaceResult, - }; - - use super::FromRow; - - impl Activity { - pub fn to_sql_prepare_statement(&self) -> &'static str { - "INSERT INTO activities (id, category, description, begin, end, duration, kind, status, tags, parent_id) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)" - } - - pub fn to_sql_execute_statement(&self) -> PaceResult<(ActivityGuid, Vec<&dyn ToSql>)> { - let category = if let Some(category) = self.category() { - category.to_sql()? - } else { - "NULL".to_sql()? - }; - - let (end, duration) = if let Some(end_opts) = self.activity_end_options().as_ref() { - let (end, duration) = end_opts.as_tuple(); - (end.to_sql()?, duration.to_sql()?) - } else { - ("NULL".to_sql()?, "NULL".to_sql()?) - }; - - let parent_id = if let Some(parent_id) = self.parent_id() { - parent_id.to_sql()? - } else { - "NULL".to_sql()? - }; - - let tags = if let Some(tags) = self.tags() { - let tags = tags - .iter() - .map(|tag| tag.to_string()) - .collect::>(); - - tags.join(",").to_sql()? - } else { - "NULL".to_sql()? - }; - - let guid = ActivityGuid::new(); - - Ok(( - guid, - vec![ - // TODO: We create a new ID here, that should probably happen - // TODO: somewhere else and needs a refactoring - &guid, - &category, - &self.description(), - &self.begin(), - &end, - &duration, - &self.kind(), - &self.status(), - &tags, - &parent_id, - ], - )) - } - } - - impl ActivityFilterKind { - pub fn to_sql_statement(&self) -> &'static str { - match self { - Self::Everything => "SELECT * FROM activities", - ActivityFilterKind::OnlyActivities => todo!(), - ActivityFilterKind::Active => { - "SELECT * FROM activities WHERE status = 'in-progress'" - } - ActivityFilterKind::ActiveIntermission => todo!(), - ActivityFilterKind::Archived => { - "SELECT * FROM activities WHERE status = 'archived'" - } - ActivityFilterKind::Ended => "SELECT * FROM activities WHERE status = 'completed'", - ActivityFilterKind::Held => "SELECT * FROM activities WHERE status = 'paused'", - ActivityFilterKind::Intermission => todo!(), - ActivityFilterKind::TimeRange(opts) => todo!(), - } - } - } - - impl FromRow for ActivityEndOptions { - fn from_row(row: &Row<'_>) -> PaceResult { - Ok(Self::new(row.get("end")?, row.get("duration")?)) - } - } - - impl FromRow for ActivityKindOptions { - fn from_row(row: &Row<'_>) -> PaceResult { - Ok(Self::with_parent_id(row.get("parent_id")?)) - } - } - - impl FromRow for Activity { - fn from_row(row: &Row<'_>) -> PaceResult { - let begin_time: PaceDateTime = row.get("begin")?; - - let description: String = row.get("description")?; - - let tags_string: String = row.get("tags")?; - - let tags = tags_string - .split(',') - .map(|tag| tag.to_string()) - .collect::>(); - - Ok(Activity::builder() - .category(Some(row.get("category")?)) // TODO: Check for None - .description(description) - .begin(begin_time) - .activity_end_options(Some(ActivityEndOptions::from_row(row)?)) // TODO: Check for None - .kind(row.get("kind")?) - .activity_kind_options(Some(ActivityKindOptions::from_row(row)?)) // TODO: Check for None - .tags(tags) - .status(row.get("status")?) - .build()) - } - } - - impl FromRow for ActivityGuid { - fn from_row(row: &Row<'_>) -> PaceResult { - Ok(row.get("guid")?) - } - } - - impl FromRow for ActivityItem { - fn from_row(row: &Row<'_>) -> PaceResult { - let activity_end_opts = ActivityEndOptions::from_row(row)?; - - let activity_kind_opts = ActivityKindOptions::from_row(row)?; - - let activity = Activity::from_row(row)?; - - let guid = ActivityGuid::from_row(row)?; - - Ok(Self::builder().guid(guid).activity(activity).build()) - } - } - - impl ToSql for ActivityGuid { - fn to_sql(&self) -> rusqlite::Result> { - Ok(rusqlite::types::ToSqlOutput::Owned( - rusqlite::types::Value::Text(self.to_string()), - )) - } - } - - impl FromSql for ActivityGuid { - fn column_result( - value: rusqlite::types::ValueRef<'_>, - ) -> rusqlite::types::FromSqlResult { - Ok(ActivityGuid::with_id( - Ulid::from_string(value.as_str()?) - .map_err(|err| rusqlite::types::FromSqlError::Other(Box::new(err)))?, - )) - } - } - - impl ToSql for ActivityKind { - fn to_sql(&self) -> rusqlite::Result> { - Ok(rusqlite::types::ToSqlOutput::Owned( - rusqlite::types::Value::Text(self.to_string()), - )) - } - } - - impl FromSql for ActivityKind { - fn column_result( - value: rusqlite::types::ValueRef<'_>, - ) -> rusqlite::types::FromSqlResult { - Ok(ActivityKind::from_str(value.as_str()?) - .map_err(|err| rusqlite::types::FromSqlError::Other(Box::new(err)))?) - } - } - - impl ToSql for ActivityStatusKind { - fn to_sql(&self) -> rusqlite::Result> { - Ok(rusqlite::types::ToSqlOutput::Owned( - rusqlite::types::Value::Text(self.to_string()), - )) - } - } - - impl FromSql for ActivityStatusKind { - fn column_result( - value: rusqlite::types::ValueRef<'_>, - ) -> rusqlite::types::FromSqlResult { - Ok(ActivityStatusKind::from_str(value.as_str()?) - .map_err(|err| rusqlite::types::FromSqlError::Other(Box::new(err)))?) - } - } -} diff --git a/crates/storage/src/storage_types.rs b/crates/storage/src/storage_types.rs new file mode 100644 index 00000000..a39fa7fc --- /dev/null +++ b/crates/storage/src/storage_types.rs @@ -0,0 +1,240 @@ +use std::{collections::HashSet, str::FromStr}; + +use pace_time::date_time::PaceDateTime; +use rusqlite::{types::FromSql, Row, ToSql}; +use ulid::Ulid; + +// use pace_time::rusqlite::*; + +use pace_core::prelude::{ + Activity, ActivityEndOptions, ActivityFilterKind, ActivityGuid, ActivityItem, ActivityKind, + ActivityKindOptions, ActivityStatusKind, PaceResult, +}; + +use crate::sqlite::FromRow; + +pub struct SqliteActivity(Activity); + +impl std::ops::Deref for SqliteActivity { + type Target = Activity; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +pub struct SqliteActivityFilterKind(ActivityFilterKind); + +impl std::ops::Deref for SqliteActivityFilterKind { + type Target = ActivityFilterKind; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub struct SqliteActivityGuid(ActivityGuid); + +impl std::ops::Deref for SqliteActivityGuid { + type Target = ActivityGuid; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +pub struct SqliteActivityKind(ActivityKind); + +impl std::ops::Deref for SqliteActivityKind { + type Target = ActivityKind; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +pub struct SqliteActivityStatusKind(ActivityStatusKind); + +impl std::ops::Deref for SqliteActivityStatusKind { + type Target = ActivityStatusKind; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl SqliteActivity { + pub fn to_sql_prepare_statement(&self) -> &'static str { + "INSERT INTO activities (id, category, description, begin, end, duration, kind, status, tags, parent_id) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)" + } + + pub fn to_sql_execute_statement(&self) -> PaceResult<(ActivityGuid, Vec<&dyn ToSql>)> { + let category = if let Some(category) = self.category() { + category.to_sql()? + } else { + "NULL".to_sql()? + }; + + let (end, duration) = if let Some(end_opts) = self.activity_end_options().as_ref() { + let (end, duration) = end_opts.as_tuple(); + (end.to_sql()?, duration.to_sql()?) + } else { + ("NULL".to_sql()?, "NULL".to_sql()?) + }; + + let parent_id = if let Some(parent_id) = self.parent_id() { + parent_id.to_sql()? + } else { + "NULL".to_sql()? + }; + + let tags = if let Some(tags) = self.tags() { + let tags = tags + .iter() + .map(|tag| tag.to_string()) + .collect::>(); + + tags.join(",").to_sql()? + } else { + "NULL".to_sql()? + }; + + let guid = ActivityGuid::new(); + + Ok(( + guid, + vec![ + // TODO: We create a new ID here, that should probably happen + // TODO: somewhere else and needs a refactoring + &guid, + &category, + &self.description(), + &self.begin(), + &end, + &duration, + &self.kind(), + &self.status(), + &tags, + &parent_id, + ], + )) + } +} + +impl SqliteActivityFilterKind { + pub fn to_sql_statement(&self) -> &'static str { + match self { + Self::Everything => "SELECT * FROM activities", + ActivityFilterKind::OnlyActivities => todo!(), + ActivityFilterKind::Active => "SELECT * FROM activities WHERE status = 'in-progress'", + ActivityFilterKind::ActiveIntermission => todo!(), + ActivityFilterKind::Archived => "SELECT * FROM activities WHERE status = 'archived'", + ActivityFilterKind::Ended => "SELECT * FROM activities WHERE status = 'completed'", + ActivityFilterKind::Held => "SELECT * FROM activities WHERE status = 'paused'", + ActivityFilterKind::Intermission => todo!(), + ActivityFilterKind::TimeRange(opts) => todo!(), + } + } +} + +impl FromRow for ActivityEndOptions { + fn from_row(row: &Row<'_>) -> PaceResult { + Ok(Self::new(row.get("end")?, row.get("duration")?)) + } +} + +impl FromRow for ActivityKindOptions { + fn from_row(row: &Row<'_>) -> PaceResult { + Ok(Self::with_parent_id(row.get("parent_id")?)) + } +} + +impl FromRow for Activity { + fn from_row(row: &Row<'_>) -> PaceResult { + let begin_time: PaceDateTime = row.get("begin")?; + + let description: String = row.get("description")?; + + let tags_string: String = row.get("tags")?; + + let tags = tags_string + .split(',') + .map(|tag| tag.to_string()) + .collect::>(); + + Ok(Activity::builder() + .category(Some(row.get("category")?)) // TODO: Check for None + .description(description) + .begin(begin_time) + .activity_end_options(Some(ActivityEndOptions::from_row(row)?)) // TODO: Check for None + .kind(row.get("kind")?) + .activity_kind_options(Some(ActivityKindOptions::from_row(row)?)) // TODO: Check for None + .tags(tags) + .status(row.get("status")?) + .build()) + } +} + +impl FromRow for ActivityGuid { + fn from_row(row: &Row<'_>) -> PaceResult { + Ok(row.get("guid")?) + } +} + +impl FromRow for ActivityItem { + fn from_row(row: &Row<'_>) -> PaceResult { + let activity_end_opts = ActivityEndOptions::from_row(row)?; + + let activity_kind_opts = ActivityKindOptions::from_row(row)?; + + let activity = Activity::from_row(row)?; + + let guid = ActivityGuid::from_row(row)?; + + Ok(Self::builder().guid(guid).activity(activity).build()) + } +} + +impl ToSql for SqliteActivityGuid { + fn to_sql(&self) -> rusqlite::Result> { + Ok(rusqlite::types::ToSqlOutput::Owned( + rusqlite::types::Value::Text(self.to_string()), + )) + } +} + +impl FromSql for SqliteActivityGuid { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + Ok(ActivityGuid::with_id( + Ulid::from_string(value.as_str()?) + .map_err(|err| rusqlite::types::FromSqlError::Other(Box::new(err)))?, + )) + } +} + +impl ToSql for SqliteActivityKind { + fn to_sql(&self) -> rusqlite::Result> { + Ok(rusqlite::types::ToSqlOutput::Owned( + rusqlite::types::Value::Text(self.to_string()), + )) + } +} + +impl FromSql for SqliteActivityKind { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + Ok(ActivityKind::from_str(value.as_str()?) + .map_err(|err| rusqlite::types::FromSqlError::Other(Box::new(err)))?) + } +} + +impl ToSql for SqliteActivityStatusKind { + fn to_sql(&self) -> rusqlite::Result> { + Ok(rusqlite::types::ToSqlOutput::Owned( + rusqlite::types::Value::Text(self.to_string()), + )) + } +} + +impl FromSql for SqliteActivityStatusKind { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + Ok(ActivityStatusKind::from_str(value.as_str()?) + .map_err(|err| rusqlite::types::FromSqlError::Other(Box::new(err)))?) + } +}