diff --git a/src/cache.rs b/src/cache.rs index 966462a..3702b73 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -14,7 +14,6 @@ use async_trait::async_trait; use chrono::{DateTime, Utc}; use crate::traits::CalDavSource; -use crate::traits::SyncSlave; use crate::traits::PartialCalendar; use crate::traits::CompleteCalendar; use crate::calendar::cached_calendar::CachedCalendar; @@ -192,16 +191,6 @@ impl CalDavSource for Cache { } } -impl SyncSlave for Cache { - fn get_last_sync(&self) -> Option> { - self.data.last_sync - } - - fn update_last_sync(&mut self, timepoint: Option>) { - self.data.last_sync = Some(timepoint.unwrap_or_else(|| Utc::now())); - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/calendar/cached_calendar.rs b/src/calendar/cached_calendar.rs index f0c4f9e..4376846 100644 --- a/src/calendar/cached_calendar.rs +++ b/src/calendar/cached_calendar.rs @@ -10,6 +10,7 @@ use crate::traits::{PartialCalendar, CompleteCalendar}; use crate::calendar::{CalendarId, SupportedComponents, SearchFilter}; use crate::Item; use crate::item::ItemId; +use crate::item::VersionTag; /// A calendar used by the [`cache`](crate::cache) module @@ -69,6 +70,32 @@ impl PartialCalendar for CachedCalendar { Ok(()) } + async fn get_item_version_tags(&self) -> Result, Box> { + Ok(self.items.iter() + .map(|(id, item)| (id.clone(), item.version_tag().clone())) + .collect() + ) + } + + async fn get_item_by_id_mut<'a>(&'a mut self, id: &ItemId) -> Option<&'a mut Item> { + self.items.get_mut(id) + } +} + +#[async_trait] +impl CompleteCalendar for CachedCalendar { + /// Returns the items that have been deleted after `since` + async fn get_items_deleted_since(&self, since: DateTime) -> Result, Box> { + Ok(self.deleted_items.range(since..) + .map(|(_key, id)| id.clone()) + .collect()) + } + + /// Returns the list of items that this calendar contains + async fn get_items(&self) -> Result, Box> { + self.get_items_modified_since(None, None).await + } + async fn get_items_modified_since(&self, since: Option>, filter: Option) -> Result, Box> { let filter = filter.unwrap_or_default(); @@ -96,28 +123,4 @@ impl PartialCalendar for CachedCalendar { Ok(map) } - - async fn get_item_ids(&self) -> Result, Box> { - Ok(self.items.keys().cloned().collect()) - } - - async fn get_item_by_id_mut<'a>(&'a mut self, id: &ItemId) -> Option<&'a mut Item> { - self.items.get_mut(id) - } -} - -#[async_trait] -impl CompleteCalendar for CachedCalendar { - /// Returns the items that have been deleted after `since` - async fn get_items_deleted_since(&self, since: DateTime) -> Result, Box> { - Ok(self.deleted_items.range(since..) - .map(|(_key, id)| id.clone()) - .collect()) - } - - /// Returns the list of items that this calendar contains - async fn get_items(&self) -> Result, Box> { - self.get_items_modified_since(None, None).await - } - } diff --git a/src/calendar/remote_calendar.rs b/src/calendar/remote_calendar.rs index 883c6c8..e985d60 100644 --- a/src/calendar/remote_calendar.rs +++ b/src/calendar/remote_calendar.rs @@ -7,8 +7,9 @@ use async_trait::async_trait; use crate::traits::PartialCalendar; use crate::calendar::SupportedComponents; use crate::calendar::CalendarId; -use crate::item::ItemId; use crate::item::Item; +use crate::item::ItemId; +use crate::item::VersionTag; use crate::resource::Resource; static TASKS_BODY: &str = r#" @@ -51,14 +52,6 @@ impl PartialCalendar for RemoteCalendar { self.supported_components } - /// Returns the items that have been last-modified after `since` - async fn get_items_modified_since(&self, since: Option>, _filter: Option) - -> Result, Box> - { - log::error!("Not implemented"); - Ok(HashMap::new()) - } - /// Get the IDs of all current items in this calendar async fn get_item_ids(&self) -> Result, Box> { let responses = crate::client::sub_request_and_extract_elems(&self.resource, "REPORT", TASKS_BODY.to_string(), "response").await?; @@ -82,6 +75,11 @@ impl PartialCalendar for RemoteCalendar { Ok(item_ids) } + async fn get_item_version_tags(&self) -> Result, Box> { + log::error!("Not implemented"); + Ok(HashMap::new()) + } + /// Returns a particular item async fn get_item_by_id_mut<'a>(&'a mut self, _id: &ItemId) -> Option<&'a mut Item> { log::error!("Not implemented"); diff --git a/src/event.rs b/src/event.rs index ff37d63..829b0ac 100644 --- a/src/event.rs +++ b/src/event.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use chrono::{Utc, DateTime}; use crate::item::ItemId; +use crate::item::VersionTag; /// TODO: implement Event one day. /// This crate currently only supports tasks, not calendar events. @@ -12,6 +13,7 @@ pub struct Event { id: ItemId, name: String, last_modified: DateTime, + version_tag: VersionTag, } impl Event { @@ -26,4 +28,8 @@ impl Event { pub fn last_modified(&self) -> DateTime { self.last_modified } + + pub fn version_tag(&self) -> &VersionTag { + &self.version_tag + } } diff --git a/src/item.rs b/src/item.rs index 86bb923..3f7facb 100644 --- a/src/item.rs +++ b/src/item.rs @@ -7,6 +7,8 @@ use url::Url; use crate::resource::Resource; + + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Item { Event(crate::event::Event), @@ -35,6 +37,13 @@ impl Item { } } + pub fn version_tag(&self) -> &VersionTag { + match self { + Item::Event(e) => e.version_tag(), + Item::Task(t) => t.version_tag(), + } + } + pub fn is_event(&self) -> bool { match &self { Item::Event(_) => true, @@ -109,3 +118,24 @@ impl Display for ItemId { write!(f, "{}", self.content) } } + + +/// A VersionTag is basically a CalDAV `ctag` or `etag`. Whenever it changes, this means the data has changed. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct VersionTag { + tag: String +} + +impl From for VersionTag { + fn from(tag: String) -> VersionTag { + Self { tag } + } +} + +impl VersionTag { + /// Generate a random VesionTag. This is only useful in tests + pub fn random() -> Self { + let random = uuid::Uuid::new_v4().to_hyphenated().to_string(); + Self { tag: random } + } +} diff --git a/src/lib.rs b/src/lib.rs index 4019ea3..d08286f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ pub use calendar::cached_calendar::CachedCalendar; mod item; pub use item::Item; pub use item::ItemId; +pub use item::VersionTag; mod task; pub use task::Task; mod event; diff --git a/src/provider.rs b/src/provider.rs index b0e1cb6..d56e63c 100644 --- a/src/provider.rs +++ b/src/provider.rs @@ -7,7 +7,6 @@ use std::marker::PhantomData; use chrono::{DateTime, Utc}; use crate::traits::{CalDavSource, CompleteCalendar}; -use crate::traits::SyncSlave; use crate::traits::PartialCalendar; use crate::Item; use crate::item::ItemId; @@ -16,7 +15,7 @@ use crate::item::ItemId; /// A data source that combines two `CalDavSources` (usually a server and a local cache), which is able to sync both sources. pub struct Provider where - L: CalDavSource + SyncSlave, + L: CalDavSource, T: CompleteCalendar, S: CalDavSource, U: PartialCalendar + Sync + Send, @@ -32,7 +31,7 @@ where impl Provider where - L: CalDavSource + SyncSlave, + L: CalDavSource, T: CompleteCalendar, S: CalDavSource, U: PartialCalendar + Sync + Send, @@ -51,19 +50,14 @@ where pub fn server(&self) -> &S { &self.server } /// Returns the data source described as the `local` pub fn local(&self) -> &L { &self.local } - /// Returns the last time the `local` source has been synced - pub fn last_sync_timestamp(&self) -> Option> { - self.local.get_last_sync() - } /// Performs a synchronisation between `local` and `server`. /// /// This bidirectional sync applies additions/deletions made on a source to the other source. /// In case of conflicts (the same item has been modified on both ends since the last sync, `server` always wins) pub async fn sync(&mut self) -> Result<(), Box> { - let last_sync = self.local.get_last_sync(); - log::info!("Starting a sync. Last sync was at {:?}", last_sync); - + log::info!("Starting a sync."); +/* let cals_server = self.server.get_calendars().await?; for (id, cal_server) in cals_server { let mut cal_server = cal_server.lock().unwrap(); @@ -77,8 +71,8 @@ where }; let mut cal_local = cal_local.lock().unwrap(); - // Step 1 - "Server always wins", so a delteion from the server must be applied locally, even if it was locally modified. - let mut local_dels = match last_sync { + // Step 1 - "Server always wins", so a deletion from the server must be applied locally, even if it was locally modified. + let mut local_dels = match cal_local.get_last_sync() { None => HashSet::new(), Some(date) => cal_local.get_items_deleted_since(date).await?, }; @@ -138,7 +132,7 @@ where } self.local.update_last_sync(None); - +*/ Ok(()) } } diff --git a/src/task.rs b/src/task.rs index d56a7ff..f19e9e0 100644 --- a/src/task.rs +++ b/src/task.rs @@ -2,6 +2,7 @@ use chrono::{Utc, DateTime}; use serde::{Deserialize, Serialize}; use crate::item::ItemId; +use crate::item::VersionTag; /// A to-do task #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -11,6 +12,8 @@ pub struct Task { /// The last modification date of this task last_modified: DateTime, + /// The version tag of this item + version_tag: VersionTag, /// The display name of the task name: String, @@ -20,11 +23,12 @@ pub struct Task { impl Task { /// Create a new Task - pub fn new(name: String, id: ItemId, last_modified: DateTime) -> Self { + pub fn new(name: String, id: ItemId, last_modified: DateTime, version_tag: VersionTag) -> Self { Self { id, name, last_modified, + version_tag, completed: false, } } @@ -33,6 +37,7 @@ impl Task { pub fn name(&self) -> &str { &self.name } pub fn completed(&self) -> bool { self.completed } pub fn last_modified(&self) -> DateTime { self.last_modified } + pub fn version_tag(&self) -> &VersionTag { &self.version_tag } fn update_last_modified(&mut self) { self.last_modified = Utc::now(); diff --git a/src/traits.rs b/src/traits.rs index 34fe2d8..ef2216e 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -7,6 +7,7 @@ use chrono::{DateTime, Utc}; use crate::item::Item; use crate::item::ItemId; +use crate::item::VersionTag; use crate::calendar::CalendarId; #[async_trait] @@ -18,14 +19,6 @@ pub trait CalDavSource { async fn get_calendar(&self, id: &CalendarId) -> Option>>; } -pub trait SyncSlave { - /// Returns the last time this source successfully synced from a master source (e.g. from a server) - /// (or None in case it has never been synchronized) - fn get_last_sync(&self) -> Option>; - /// Update the last sync timestamp to now, or to a custom time in case `timepoint` is `Some` - fn update_last_sync(&mut self, timepoint: Option>); -} - /// A calendar we have a partial knowledge of. /// /// Usually, this is a calendar from a remote source, that is synced to a CompleteCalendar @@ -40,12 +33,8 @@ pub trait PartialCalendar { /// Returns the supported kinds of components for this calendar fn supported_components(&self) -> crate::calendar::SupportedComponents; - /// Returns the items that have been last-modified after `since` - async fn get_items_modified_since(&self, since: Option>, filter: Option) - -> Result, Box>; - - /// Get the IDs of all current items in this calendar - async fn get_item_ids(&self) -> Result, Box>; + /// Get the IDs and the version tags of every item in this calendar + async fn get_item_version_tags(&self) -> Result, Box>; /// Returns a particular item async fn get_item_by_id_mut<'a>(&'a mut self, id: &ItemId) -> Option<&'a mut Item>; @@ -67,6 +56,14 @@ pub trait PartialCalendar { self.supported_components().contains(crate::calendar::SupportedComponents::EVENT) } + /// Get the IDs of all current items in this calendar + async fn get_item_ids(&self) -> Result, Box> { + let items = self.get_item_version_tags().await?; + Ok(items.iter() + .map(|(id, _tag)| id.clone()) + .collect()) + } + /// Finds the IDs of the items that are missing compared to a reference set async fn find_deletions_from(&self, reference_set: HashSet) -> Result, Box> { let current_items = self.get_item_ids().await?; @@ -84,6 +81,10 @@ pub trait CompleteCalendar : PartialCalendar { /// See also [`PartialCalendar::get_items_deleted_since`] async fn get_items_deleted_since(&self, since: DateTime) -> Result, Box>; + /// Returns the items that have been last-modified after `since` + async fn get_items_modified_since(&self, since: Option>, filter: Option) + -> Result, Box>; + /// Returns the list of items that this calendar contains async fn get_items(&self) -> Result, Box>; } diff --git a/tests/caldav_client.rs b/tests/caldav_client.rs index ec11660..0e5f591 100644 --- a/tests/caldav_client.rs +++ b/tests/caldav_client.rs @@ -53,9 +53,9 @@ async fn show_calendars() { } println!(" Most recent changes:"); - for (_id, task) in cal.get_items_modified_since(None, None).await.unwrap() { - my_tasks::utils::print_task(task); - } + // for (_id, task) in cal.get_item_version_tags(None, None).await.unwrap() { + // my_tasks::utils::print_task(task); + // } } } diff --git a/tests/sync.rs b/tests/sync.rs index 943d608..6e0601f 100644 --- a/tests/sync.rs +++ b/tests/sync.rs @@ -4,11 +4,12 @@ use std::sync::{Arc, Mutex}; use chrono::{Utc, TimeZone}; use url::Url; -use my_tasks::traits::{CalDavSource, SyncSlave}; +use my_tasks::traits::CalDavSource; use my_tasks::traits::PartialCalendar; use my_tasks::cache::Cache; use my_tasks::Item; use my_tasks::ItemId; +use my_tasks::VersionTag; use my_tasks::Task; use my_tasks::calendar::cached_calendar::CachedCalendar; use my_tasks::Provider; @@ -53,23 +54,23 @@ async fn populate_test_provider() -> Provider Provider Provider