From 849cbbc07a764d4e7d58695d3cccd231347f88bd Mon Sep 17 00:00:00 2001 From: daladim Date: Sun, 18 Apr 2021 00:13:39 +0200 Subject: [PATCH] Added the update_item function --- src/calendar/cached_calendar.rs | 62 ++++++++++++++++++++++++--------- src/calendar/remote_calendar.rs | 31 ++++++++++++++++- src/item.rs | 5 +++ src/mock_behaviour.rs | 6 ++++ src/traits.rs | 4 +++ 5 files changed, 90 insertions(+), 18 deletions(-) diff --git a/src/calendar/cached_calendar.rs b/src/calendar/cached_calendar.rs index e0da5c4..0533a43 100644 --- a/src/calendar/cached_calendar.rs +++ b/src/calendar/cached_calendar.rs @@ -36,19 +36,41 @@ impl CachedCalendar { self.mock_behaviour = mock_behaviour; } - /// Add an item - async fn regular_add_item(&mut self, item: Item) -> Result> { - // TODO: here (and in the remote version, display an errror in case we overwrite something?) + + #[cfg(feature = "local_calendar_mocks_remote_calendars")] + async fn add_item_maybe_mocked(&mut self, item: Item) -> Result> { + self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_add_item())?; + + if self.mock_behaviour.is_some() { + self.add_or_update_item_force_synced(item).await + } else { + self.regular_add_or_update_item(item).await + } + } + + #[cfg(feature = "local_calendar_mocks_remote_calendars")] + async fn update_item_maybe_mocked(&mut self, item: Item) -> Result> { + self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_update_item())?; + + if self.mock_behaviour.is_some() { + self.add_or_update_item_force_synced(item).await + } else { + self.regular_add_or_update_item(item).await + } + } + + /// Add or update an item + async fn regular_add_or_update_item(&mut self, item: Item) -> Result> { let ss_clone = item.sync_status().clone(); - log::debug!("Adding an item with {:?}", ss_clone); + log::debug!("Adding or updating an item with {:?}", ss_clone); self.items.insert(item.id().clone(), item); Ok(ss_clone) } - /// Add an item, but force a "synced" SyncStatus. This is the typical behaviour on a remote calendar + /// Add or update an item, but force a "synced" SyncStatus. This is the normal behaviour that would happen on a server #[cfg(feature = "local_calendar_mocks_remote_calendars")] - async fn add_item_force_synced(&mut self, mut item: Item) -> Result> { - log::debug!("Adding an item, but forces a synced SyncStatus"); + async fn add_or_update_item_force_synced(&mut self, mut item: Item) -> Result> { + log::debug!("Adding or updating an item, but forces a synced SyncStatus"); match item.sync_status() { SyncStatus::Synced(_) => (), _ => item.set_sync_status(SyncStatus::random_synced()), @@ -108,20 +130,26 @@ impl BaseCalendar for CachedCalendar { self.supported_components } - #[cfg(not(feature = "local_calendar_mocks_remote_calendars"))] async fn add_item(&mut self, item: Item) -> Result> { - self.regular_add_item(item).await + if self.items.contains_key(item.id()) { + return Err(format!("Item {:?} cannot be added, it exists already", item.id()).into()); } - #[cfg(feature = "local_calendar_mocks_remote_calendars")] - async fn add_item(&mut self, item: Item) -> Result> { - #[cfg(feature = "local_calendar_mocks_remote_calendars")] - self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_add_item())?; + #[cfg(not(feature = "local_calendar_mocks_remote_calendars"))] + return self.regular_add_or_update_item(item).await; - if self.mock_behaviour.is_some() { - self.add_item_force_synced(item).await - } else { - self.regular_add_item(item).await + #[cfg(feature = "local_calendar_mocks_remote_calendars")] + return self.add_item_maybe_mocked(item).await; + } + + async fn update_item(&mut self, item: Item) -> Result> { + if self.items.contains_key(item.id()) == false { + return Err(format!("Item {:?} cannot be updated, it does not already exist", item.id()).into()); } + #[cfg(not(feature = "local_calendar_mocks_remote_calendars"))] + return self.regular_add_or_update_item(item).await; + + #[cfg(feature = "local_calendar_mocks_remote_calendars")] + return self.update_item_maybe_mocked(item).await; } } diff --git a/src/calendar/remote_calendar.rs b/src/calendar/remote_calendar.rs index 41f48a7..a148162 100644 --- a/src/calendar/remote_calendar.rs +++ b/src/calendar/remote_calendar.rs @@ -48,7 +48,6 @@ impl BaseCalendar for RemoteCalendar { self.supported_components } - /// Add an item into this calendar async fn add_item(&mut self, item: Item) -> Result> { let ical_text = crate::ical::build_from(&item)?; @@ -72,6 +71,36 @@ impl BaseCalendar for RemoteCalendar { } } } + + async fn update_item(&mut self, item: Item) -> Result> { + let old_etag = match item.sync_status() { + SyncStatus::NotSynced => return Err("Cannot update an item that has not been synced already".into()), + SyncStatus::Synced(_) => return Err("Cannot update an item that has not changed".into()), + SyncStatus::LocallyModified(etag) => etag, + SyncStatus::LocallyDeleted(etag) => etag, + }; + let ical_text = crate::ical::build_from(&item)?; + + let request = reqwest::Client::new() + .put(item.id().as_url().clone()) + .header("If-Match", old_etag.as_str()) + .header(CONTENT_TYPE, "text/calendar") + .header(CONTENT_LENGTH, ical_text.len()) + .basic_auth(self.resource.username(), Some(self.resource.password())) + .body(ical_text) + .send() + .await?; + + let reply_hdrs = request.headers(); + match reply_hdrs.get("ETag") { + None => Err(format!("No ETag in these response headers: {:?} (request was {:?})", reply_hdrs, item.id()).into()), + Some(etag) => { + let vtag_str = etag.to_str()?; + let vtag = VersionTag::from(String::from(vtag_str)); + Ok(SyncStatus::Synced(vtag)) + } + } + } } #[async_trait] diff --git a/src/item.rs b/src/item.rs index 87512ac..d223cb9 100644 --- a/src/item.rs +++ b/src/item.rs @@ -190,6 +190,11 @@ impl From for VersionTag { } impl VersionTag { + /// Get the inner version tag (usually a WebDAV `ctag` or `etag`) + pub fn as_str(&self) -> &str { + &self.tag + } + /// Generate a random VesionTag #[cfg(feature = "local_calendar_mocks_remote_calendars")] pub fn random() -> Self { diff --git a/src/mock_behaviour.rs b/src/mock_behaviour.rs index 87e8d63..af4f78a 100644 --- a/src/mock_behaviour.rs +++ b/src/mock_behaviour.rs @@ -17,6 +17,7 @@ pub struct MockBehaviour { // From the BaseCalendar trait pub add_item_behaviour: (u32, u32), + pub update_item_behaviour: (u32, u32), // From the DavCalendar trait pub get_item_version_tags_behaviour: (u32, u32), @@ -37,6 +38,7 @@ impl MockBehaviour { //get_calendar_behaviour: (0, n_fails), create_calendar_behaviour: (0, n_fails), add_item_behaviour: (0, n_fails), + update_item_behaviour: (0, n_fails), get_item_version_tags_behaviour: (0, n_fails), get_item_by_id_behaviour: (0, n_fails), delete_item_behaviour: (0, n_fails), @@ -73,6 +75,10 @@ impl MockBehaviour { if self.is_suspended { return Ok(()) } decrement(&mut self.add_item_behaviour, "add_item") } + pub fn can_update_item(&mut self) -> Result<(), Box> { + if self.is_suspended { return Ok(()) } + decrement(&mut self.update_item_behaviour, "update_item") + } pub fn can_get_item_version_tags(&mut self) -> Result<(), Box> { if self.is_suspended { return Ok(()) } decrement(&mut self.get_item_version_tags_behaviour, "get_item_version_tags") diff --git a/src/traits.rs b/src/traits.rs index 645479c..c3926eb 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -42,6 +42,10 @@ pub trait BaseCalendar { /// For remote calendars, the sync status is updated by the server async fn add_item(&mut self, item: Item) -> Result>; + /// Update an item that already exists in this calendar and returns its new `SyncStatus` + /// This replaces a given item at a given URL + async fn update_item(&mut self, item: Item) -> Result>; + /// Returns whether this calDAV calendar supports to-do items fn supports_todo(&self) -> bool { self.supported_components().contains(crate::calendar::SupportedComponents::TODO)