From 2f7c14d0aa1ad3dcbd5563af939cd7ec92db4386 Mon Sep 17 00:00:00 2001 From: daladim Date: Mon, 15 Nov 2021 23:52:26 +0100 Subject: [PATCH 1/2] Better distinction ID/UID --- examples/provider-sync.rs | 14 ++--- src/cache.rs | 8 +-- src/calendar/cached_calendar.rs | 62 +++++++++++----------- src/calendar/remote_calendar.rs | 29 +++++------ src/client.rs | 2 +- src/event.rs | 10 ++-- src/ical/parser.rs | 34 ++++++------ src/item.rs | 72 +------------------------- src/provider/mod.rs | 5 +- src/task.rs | 23 +++++---- src/traits.rs | 26 +++++----- src/utils/mod.rs | 10 +++- tests/scenarii.rs | 91 +++++++++++++++++---------------- 13 files changed, 165 insertions(+), 221 deletions(-) diff --git a/examples/provider-sync.rs b/examples/provider-sync.rs index 8bbe9d0..a93dbf5 100644 --- a/examples/provider-sync.rs +++ b/examples/provider-sync.rs @@ -3,13 +3,13 @@ use std::path::Path; use chrono::{Utc}; +use url::Url; use kitchen_fridge::{client::Client, traits::CalDavSource}; use kitchen_fridge::calendar::{CalendarId, SupportedComponents}; use kitchen_fridge::Item; use kitchen_fridge::Task; use kitchen_fridge::task::CompletionStatus; -use kitchen_fridge::item::ItemId; use kitchen_fridge::cache::Cache; use kitchen_fridge::CalDavProvider; use kitchen_fridge::traits::BaseCalendar; @@ -100,7 +100,7 @@ async fn add_items_and_sync_again(provider: &mut CalDavProvider) let changed_calendar_id: CalendarId = EXAMPLE_EXISTING_CALENDAR_URL.parse().unwrap(); let new_task_name = "This is a new task we're adding as an example, with ÜTF-8 characters"; let new_task = Task::new(String::from(new_task_name), false, &changed_calendar_id); - let new_id = new_task.id().clone(); + let new_url = new_task.url().clone(); provider.local().get_calendar(&changed_calendar_id).await.unwrap() .lock().unwrap().add_item(Item::Task(new_task)).await.unwrap(); @@ -112,20 +112,20 @@ async fn add_items_and_sync_again(provider: &mut CalDavProvider) } provider.local().save_to_folder().unwrap(); - complete_item_and_sync_again(provider, &changed_calendar_id, &new_id).await; + complete_item_and_sync_again(provider, &changed_calendar_id, &new_url).await; } async fn complete_item_and_sync_again( provider: &mut CalDavProvider, changed_calendar_id: &CalendarId, - id_to_complete: &ItemId) + url_to_complete: &Url) { println!("\nNow, we'll mark this last task as completed, and run the sync again."); pause(); let completion_status = CompletionStatus::Completed(Some(Utc::now())); provider.local().get_calendar(changed_calendar_id).await.unwrap() - .lock().unwrap().get_item_by_id_mut(id_to_complete).await.unwrap() + .lock().unwrap().get_item_by_id_mut(url_to_complete).await.unwrap() .unwrap_task_mut() .set_completion_status(completion_status); @@ -136,13 +136,13 @@ async fn complete_item_and_sync_again( } provider.local().save_to_folder().unwrap(); - remove_items_and_sync_again(provider, changed_calendar_id, id_to_complete).await; + remove_items_and_sync_again(provider, changed_calendar_id, url_to_complete).await; } async fn remove_items_and_sync_again( provider: &mut CalDavProvider, changed_calendar_id: &CalendarId, - id_to_remove: &ItemId) + id_to_remove: &Url) { println!("\nNow, we'll delete this last task, and run the sync again."); pause(); diff --git a/src/cache.rs b/src/cache.rs index bc12968..926ec43 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -86,7 +86,7 @@ impl Cache { continue; }, Ok(cal) => - data.calendars.insert(cal.id().clone(), Arc::new(Mutex::new(cal))), + data.calendars.insert(cal.url().clone(), Arc::new(Mutex::new(cal))), }; } }, @@ -259,13 +259,13 @@ mod tests { { let mut bucket_list = bucket_list.lock().unwrap(); - let cal_id = bucket_list.id().clone(); + let cal_url = bucket_list.url().clone(); bucket_list.add_item(Item::Task(Task::new( - String::from("Attend a concert of JS Bach"), false, &cal_id + String::from("Attend a concert of JS Bach"), false, &cal_url ))).await.unwrap(); bucket_list.add_item(Item::Task(Task::new( - String::from("Climb the Lighthouse of Alexandria"), true, &cal_id + String::from("Climb the Lighthouse of Alexandria"), true, &cal_url ))).await.unwrap(); } diff --git a/src/calendar/cached_calendar.rs b/src/calendar/cached_calendar.rs index ec4f8a0..aa14db8 100644 --- a/src/calendar/cached_calendar.rs +++ b/src/calendar/cached_calendar.rs @@ -4,12 +4,12 @@ use std::error::Error; use serde::{Deserialize, Serialize}; use async_trait::async_trait; use csscolorparser::Color; +use url::Url; use crate::item::SyncStatus; use crate::traits::{BaseCalendar, CompleteCalendar}; use crate::calendar::{CalendarId, SupportedComponents}; use crate::Item; -use crate::item::ItemId; #[cfg(feature = "local_calendar_mocks_remote_calendars")] use std::sync::{Arc, Mutex}; @@ -23,14 +23,14 @@ use crate::mock_behaviour::MockBehaviour; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct CachedCalendar { name: String, - id: CalendarId, + url: Url, supported_components: SupportedComponents, color: Option, #[cfg(feature = "local_calendar_mocks_remote_calendars")] #[serde(skip)] mock_behaviour: Option>>, - items: HashMap, + items: HashMap, } impl CachedCalendar { @@ -65,7 +65,7 @@ impl CachedCalendar { fn regular_add_or_update_item(&mut self, item: Item) -> Result> { let ss_clone = item.sync_status().clone(); log::debug!("Adding or updating an item with {:?}", ss_clone); - self.items.insert(item.id().clone(), item); + self.items.insert(item.url().clone(), item); Ok(ss_clone) } @@ -78,7 +78,7 @@ impl CachedCalendar { _ => item.set_sync_status(SyncStatus::random_synced()), }; let ss_clone = item.sync_status().clone(); - self.items.insert(item.id().clone(), item); + self.items.insert(item.url().clone(), item); Ok(ss_clone) } @@ -86,7 +86,7 @@ impl CachedCalendar { #[cfg(any(test, feature = "integration_tests"))] pub async fn has_same_observable_content_as(&self, other: &CachedCalendar) -> Result> { if self.name != other.name - || self.id != other.id + || self.url != other.url || self.supported_components != other.supported_components || self.color != other.color { @@ -119,35 +119,35 @@ impl CachedCalendar { } /// The non-async version of [`Self::get_item_ids`] - pub fn get_item_ids_sync(&self) -> Result, Box> { + pub fn get_item_ids_sync(&self) -> Result, Box> { Ok(self.items.iter() - .map(|(id, _)| id.clone()) + .map(|(url, _)| url.clone()) .collect() ) } /// The non-async version of [`Self::get_items`] - pub fn get_items_sync(&self) -> Result, Box> { + pub fn get_items_sync(&self) -> Result, Box> { Ok(self.items.iter() - .map(|(id, item)| (id.clone(), item)) + .map(|(url, item)| (url.clone(), item)) .collect() ) } /// The non-async version of [`Self::get_item_by_id`] - pub fn get_item_by_id_sync<'a>(&'a self, id: &ItemId) -> Option<&'a Item> { + pub fn get_item_by_id_sync<'a>(&'a self, id: &Url) -> Option<&'a Item> { self.items.get(id) } /// The non-async version of [`Self::get_item_by_id_mut`] - pub fn get_item_by_id_mut_sync<'a>(&'a mut self, id: &ItemId) -> Option<&'a mut Item> { + pub fn get_item_by_id_mut_sync<'a>(&'a mut self, id: &Url) -> Option<&'a mut Item> { self.items.get_mut(id) } /// The non-async version of [`Self::add_item`] pub fn add_item_sync(&mut self, item: Item) -> Result> { - if self.items.contains_key(item.id()) { - return Err(format!("Item {:?} cannot be added, it exists already", item.id()).into()); + if self.items.contains_key(item.url()) { + return Err(format!("Item {:?} cannot be added, it exists already", item.url()).into()); } #[cfg(not(feature = "local_calendar_mocks_remote_calendars"))] return self.regular_add_or_update_item(item); @@ -158,8 +158,8 @@ impl CachedCalendar { /// The non-async version of [`Self::update_item`] pub fn update_item_sync(&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()); + if self.items.contains_key(item.url()) == false { + return Err(format!("Item {:?} cannot be updated, it does not already exist", item.url()).into()); } #[cfg(not(feature = "local_calendar_mocks_remote_calendars"))] return self.regular_add_or_update_item(item); @@ -169,7 +169,7 @@ impl CachedCalendar { } /// The non-async version of [`Self::mark_for_deletion`] - pub fn mark_for_deletion_sync(&mut self, item_id: &ItemId) -> Result<(), Box> { + pub fn mark_for_deletion_sync(&mut self, item_id: &Url) -> Result<(), Box> { match self.items.get_mut(item_id) { None => Err("no item for this key".into()), Some(item) => { @@ -197,7 +197,7 @@ impl CachedCalendar { } /// The non-async version of [`Self::immediately_delete_item`] - pub fn immediately_delete_item_sync(&mut self, item_id: &ItemId) -> Result<(), Box> { + pub fn immediately_delete_item_sync(&mut self, item_id: &Url) -> Result<(), Box> { match self.items.remove(item_id) { None => Err(format!("Item {} is absent from this calendar", item_id).into()), Some(_) => Ok(()) @@ -213,8 +213,8 @@ impl BaseCalendar for CachedCalendar { &self.name } - fn id(&self) -> &CalendarId { - &self.id + fn url(&self) -> &Url { + &self.url } fn supported_components(&self) -> SupportedComponents { @@ -236,36 +236,36 @@ impl BaseCalendar for CachedCalendar { #[async_trait] impl CompleteCalendar for CachedCalendar { - fn new(name: String, id: CalendarId, supported_components: SupportedComponents, color: Option) -> Self { + fn new(name: String, url: CalendarId, supported_components: SupportedComponents, color: Option) -> Self { Self { - name, id, supported_components, color, + name, url, supported_components, color, #[cfg(feature = "local_calendar_mocks_remote_calendars")] mock_behaviour: None, items: HashMap::new(), } } - async fn get_item_ids(&self) -> Result, Box> { + async fn get_item_ids(&self) -> Result, Box> { self.get_item_ids_sync() } - async fn get_items(&self) -> Result, Box> { + async fn get_items(&self) -> Result, Box> { self.get_items_sync() } - async fn get_item_by_id<'a>(&'a self, id: &ItemId) -> Option<&'a Item> { + async fn get_item_by_id<'a>(&'a self, id: &Url) -> Option<&'a Item> { self.get_item_by_id_sync(id) } - async fn get_item_by_id_mut<'a>(&'a mut self, id: &ItemId) -> Option<&'a mut Item> { + async fn get_item_by_id_mut<'a>(&'a mut self, id: &Url) -> Option<&'a mut Item> { self.get_item_by_id_mut_sync(id) } - async fn mark_for_deletion(&mut self, item_id: &ItemId) -> Result<(), Box> { + async fn mark_for_deletion(&mut self, item_id: &Url) -> Result<(), Box> { self.mark_for_deletion_sync(item_id) } - async fn immediately_delete_item(&mut self, item_id: &ItemId) -> Result<(), Box> { + async fn immediately_delete_item(&mut self, item_id: &Url) -> Result<(), Box> { self.immediately_delete_item_sync(item_id) } } @@ -286,7 +286,7 @@ impl DavCalendar for CachedCalendar { crate::traits::CompleteCalendar::new(name, resource.url().clone(), supported_components, color) } - async fn get_item_version_tags(&self) -> Result, Box> { + async fn get_item_version_tags(&self) -> Result, Box> { #[cfg(feature = "local_calendar_mocks_remote_calendars")] self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_get_item_version_tags())?; @@ -307,14 +307,14 @@ impl DavCalendar for CachedCalendar { Ok(result) } - async fn get_item_by_id(&self, id: &ItemId) -> Result, Box> { + async fn get_item_by_id(&self, id: &Url) -> Result, Box> { #[cfg(feature = "local_calendar_mocks_remote_calendars")] self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_get_item_by_id())?; Ok(self.items.get(id).cloned()) } - async fn delete_item(&mut self, item_id: &ItemId) -> Result<(), Box> { + async fn delete_item(&mut self, item_id: &Url) -> Result<(), Box> { #[cfg(feature = "local_calendar_mocks_remote_calendars")] self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_delete_item())?; diff --git a/src/calendar/remote_calendar.rs b/src/calendar/remote_calendar.rs index 14912d4..050c78a 100644 --- a/src/calendar/remote_calendar.rs +++ b/src/calendar/remote_calendar.rs @@ -5,13 +5,12 @@ use std::sync::Mutex; use async_trait::async_trait; use reqwest::{header::CONTENT_TYPE, header::CONTENT_LENGTH}; use csscolorparser::Color; +use url::Url; use crate::traits::BaseCalendar; use crate::traits::DavCalendar; use crate::calendar::SupportedComponents; -use crate::calendar::CalendarId; use crate::item::Item; -use crate::item::ItemId; use crate::item::VersionTag; use crate::item::SyncStatus; use crate::resource::Resource; @@ -40,13 +39,13 @@ pub struct RemoteCalendar { supported_components: SupportedComponents, color: Option, - cached_version_tags: Mutex>>, + cached_version_tags: Mutex>>, } #[async_trait] impl BaseCalendar for RemoteCalendar { fn name(&self) -> &str { &self.name } - fn id(&self) -> &CalendarId { &self.resource.url() } + fn url(&self) -> &Url { &self.resource.url() } fn supported_components(&self) -> crate::calendar::SupportedComponents { self.supported_components } @@ -58,7 +57,7 @@ impl BaseCalendar for RemoteCalendar { let ical_text = crate::ical::build_from(&item)?; let response = reqwest::Client::new() - .put(item.id().as_url().clone()) + .put(item.url().clone()) .header("If-None-Match", "*") .header(CONTENT_TYPE, "text/calendar") .header(CONTENT_LENGTH, ical_text.len()) @@ -73,7 +72,7 @@ impl BaseCalendar for RemoteCalendar { let reply_hdrs = response.headers(); match reply_hdrs.get("ETag") { - None => Err(format!("No ETag in these response headers: {:?} (request was {:?})", reply_hdrs, item.id()).into()), + None => Err(format!("No ETag in these response headers: {:?} (request was {:?})", reply_hdrs, item.url()).into()), Some(etag) => { let vtag_str = etag.to_str()?; let vtag = VersionTag::from(String::from(vtag_str)); @@ -92,7 +91,7 @@ impl BaseCalendar for RemoteCalendar { let ical_text = crate::ical::build_from(&item)?; let request = reqwest::Client::new() - .put(item.id().as_url().clone()) + .put(item.url().clone()) .header("If-Match", old_etag.as_str()) .header(CONTENT_TYPE, "text/calendar") .header(CONTENT_LENGTH, ical_text.len()) @@ -107,7 +106,7 @@ impl BaseCalendar for RemoteCalendar { 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()), + None => Err(format!("No ETag in these response headers: {:?} (request was {:?})", reply_hdrs, item.url()).into()), Some(etag) => { let vtag_str = etag.to_str()?; let vtag = VersionTag::from(String::from(vtag_str)); @@ -127,7 +126,7 @@ impl DavCalendar for RemoteCalendar { } - async fn get_item_version_tags(&self) -> Result, Box> { + async fn get_item_version_tags(&self) -> Result, Box> { if let Some(map) = &*self.cached_version_tags.lock().unwrap() { log::debug!("Version tags are already cached."); return Ok(map.clone()); @@ -145,7 +144,7 @@ impl DavCalendar for RemoteCalendar { continue; }, Some(resource) => { - ItemId::from(&resource) + resource.url().clone() }, }; @@ -159,7 +158,7 @@ impl DavCalendar for RemoteCalendar { } }; - items.insert(item_id, version_tag); + items.insert(item_id.clone(), version_tag); } // Note: the mutex cannot be locked during this whole async function, but it can safely be re-entrant (this will just waste an unnecessary request) @@ -167,9 +166,9 @@ impl DavCalendar for RemoteCalendar { Ok(items) } - async fn get_item_by_id(&self, id: &ItemId) -> Result, Box> { + async fn get_item_by_id(&self, id: &Url) -> Result, Box> { let res = reqwest::Client::new() - .get(id.as_url().clone()) + .get(id.clone()) .header(CONTENT_TYPE, "text/calendar") .basic_auth(self.resource.username(), Some(self.resource.password())) .send() @@ -192,9 +191,9 @@ impl DavCalendar for RemoteCalendar { Ok(Some(item)) } - async fn delete_item(&mut self, item_id: &ItemId) -> Result<(), Box> { + async fn delete_item(&mut self, item_id: &Url) -> Result<(), Box> { let del_response = reqwest::Client::new() - .delete(item_id.as_url().clone()) + .delete(item_id.clone()) .basic_auth(self.resource.username(), Some(self.resource.password())) .send() .await?; diff --git a/src/client.rs b/src/client.rs index 112fdc0..242a1a1 100644 --- a/src/client.rs +++ b/src/client.rs @@ -216,7 +216,7 @@ impl Client { let this_calendar = RemoteCalendar::new(display_name, this_calendar_url, supported_components, this_calendar_color); log::info!("Found calendar {}", this_calendar.name()); - calendars.insert(this_calendar.id().clone(), Arc::new(Mutex::new(this_calendar))); + calendars.insert(this_calendar.url().clone(), Arc::new(Mutex::new(this_calendar))); } let mut replies = self.cached_replies.lock().unwrap(); diff --git a/src/event.rs b/src/event.rs index c2149ca..2821242 100644 --- a/src/event.rs +++ b/src/event.rs @@ -2,15 +2,15 @@ use serde::{Deserialize, Serialize}; use chrono::{DateTime, Utc}; +use url::Url; -use crate::item::ItemId; use crate::item::SyncStatus; /// TODO: implement `Event` one day. /// This crate currently only supports tasks, not calendar events. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Event { - id: ItemId, + uid: String, name: String, sync_status: SyncStatus, } @@ -20,12 +20,12 @@ impl Event { unimplemented!(); } - pub fn id(&self) -> &ItemId { - &self.id + pub fn url(&self) -> &Url { + unimplemented!(); } pub fn uid(&self) -> &str { - unimplemented!() + &self.uid } pub fn name(&self) -> &str { diff --git a/src/ical/parser.rs b/src/ical/parser.rs index 8be5da5..3bdeadd 100644 --- a/src/ical/parser.rs +++ b/src/ical/parser.rs @@ -4,22 +4,22 @@ use std::error::Error; use ical::parser::ical::component::{IcalCalendar, IcalEvent, IcalTodo}; use chrono::{DateTime, TimeZone, Utc}; +use url::Url; use crate::Item; use crate::item::SyncStatus; -use crate::item::ItemId; use crate::Task; use crate::task::CompletionStatus; use crate::Event; /// Parse an iCal file into the internal representation [`crate::Item`] -pub fn parse(content: &str, item_id: ItemId, sync_status: SyncStatus) -> Result> { +pub fn parse(content: &str, item_url: Url, sync_status: SyncStatus) -> Result> { let mut reader = ical::IcalParser::new(content.as_bytes()); let parsed_item = match reader.next() { - None => return Err(format!("Invalid iCal data to parse for item {}", item_id).into()), + None => return Err(format!("Invalid iCal data to parse for item {}", item_url).into()), Some(item) => match item { - Err(err) => return Err(format!("Unable to parse iCal data for item {}: {}", item_id, err).into()), + Err(err) => return Err(format!("Unable to parse iCal data for item {}: {}", item_url, err).into()), Ok(item) => item, } }; @@ -80,15 +80,15 @@ pub fn parse(content: &str, item_id: ItemId, sync_status: SyncStatus) -> Result< } let name = match name { Some(name) => name, - None => return Err(format!("Missing name for item {}", item_id).into()), + None => return Err(format!("Missing name for item {}", item_url).into()), }; let uid = match uid { Some(uid) => uid, - None => return Err(format!("Missing UID for item {}", item_id).into()), + None => return Err(format!("Missing UID for item {}", item_url).into()), }; let last_modified = match last_modified { Some(dt) => dt, - None => return Err(format!("Missing DTSTAMP for item {}, but this is required by RFC5545", item_id).into()), + None => return Err(format!("Missing DTSTAMP for item {}, but this is required by RFC5545", item_url).into()), }; let completion_status = match completed { false => { @@ -100,7 +100,7 @@ pub fn parse(content: &str, item_id: ItemId, sync_status: SyncStatus) -> Result< true => CompletionStatus::Completed(completion_date), }; - Item::Task(Task::new_with_parameters(name, uid, item_id, completion_status, sync_status, creation_date, last_modified, ical_prod_id, extra_parameters)) + Item::Task(Task::new_with_parameters(name, uid, item_url, completion_status, sync_status, creation_date, last_modified, ical_prod_id, extra_parameters)) }, }; @@ -244,13 +244,13 @@ END:VCALENDAR fn test_ical_parsing() { let version_tag = VersionTag::from(String::from("test-tag")); let sync_status = SyncStatus::Synced(version_tag); - let item_id: ItemId = "http://some.id/for/testing".parse().unwrap(); + let item_url: Url = "http://some.id/for/testing".parse().unwrap(); - let item = parse(EXAMPLE_ICAL, item_id.clone(), sync_status.clone()).unwrap(); + let item = parse(EXAMPLE_ICAL, item_url.clone(), sync_status.clone()).unwrap(); let task = item.unwrap_task(); assert_eq!(task.name(), "Do not forget to do this"); - assert_eq!(task.id(), &item_id); + assert_eq!(task.url(), &item_url); assert_eq!(task.uid(), "0633de27-8c32-42be-bcb8-63bc879c6185@some-domain.com"); assert_eq!(task.completed(), false); assert_eq!(task.completion_status(), &CompletionStatus::Uncompleted); @@ -262,9 +262,9 @@ END:VCALENDAR fn test_completed_ical_parsing() { let version_tag = VersionTag::from(String::from("test-tag")); let sync_status = SyncStatus::Synced(version_tag); - let item_id: ItemId = "http://some.id/for/testing".parse().unwrap(); + let item_url: Url = "http://some.id/for/testing".parse().unwrap(); - let item = parse(EXAMPLE_ICAL_COMPLETED, item_id.clone(), sync_status.clone()).unwrap(); + let item = parse(EXAMPLE_ICAL_COMPLETED, item_url.clone(), sync_status.clone()).unwrap(); let task = item.unwrap_task(); assert_eq!(task.completed(), true); @@ -275,9 +275,9 @@ END:VCALENDAR fn test_completed_without_date_ical_parsing() { let version_tag = VersionTag::from(String::from("test-tag")); let sync_status = SyncStatus::Synced(version_tag); - let item_id: ItemId = "http://some.id/for/testing".parse().unwrap(); + let item_url: Url = "http://some.id/for/testing".parse().unwrap(); - let item = parse(EXAMPLE_ICAL_COMPLETED_WITHOUT_A_COMPLETION_DATE, item_id.clone(), sync_status.clone()).unwrap(); + let item = parse(EXAMPLE_ICAL_COMPLETED_WITHOUT_A_COMPLETION_DATE, item_url.clone(), sync_status.clone()).unwrap(); let task = item.unwrap_task(); assert_eq!(task.completed(), true); @@ -288,9 +288,9 @@ END:VCALENDAR fn test_multiple_items_in_ical() { let version_tag = VersionTag::from(String::from("test-tag")); let sync_status = SyncStatus::Synced(version_tag); - let item_id: ItemId = "http://some.id/for/testing".parse().unwrap(); + let item_url: Url = "http://some.id/for/testing".parse().unwrap(); - let item = parse(EXAMPLE_MULTIPLE_ICAL, item_id.clone(), sync_status.clone()); + let item = parse(EXAMPLE_MULTIPLE_ICAL, item_url.clone(), sync_status.clone()); assert!(item.is_err()); } } diff --git a/src/item.rs b/src/item.rs index 9c45056..8d457d0 100644 --- a/src/item.rs +++ b/src/item.rs @@ -1,17 +1,10 @@ //! CalDAV items (todo, events, journals...) // TODO: move Event and Task to nest them in crate::items::calendar::Calendar? -use std::fmt::{Display, Formatter}; -use std::str::FromStr; - -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Serialize}; use url::Url; use chrono::{DateTime, Utc}; -use crate::resource::Resource; -use crate::calendar::CalendarId; - - #[derive(Clone, Debug, Serialize, Deserialize)] pub enum Item { @@ -32,7 +25,7 @@ macro_rules! synthetise_common_getter { } impl Item { - synthetise_common_getter!(id, &ItemId); + synthetise_common_getter!(url, &Url); synthetise_common_getter!(uid, &str); synthetise_common_getter!(name, &str); synthetise_common_getter!(creation_date, Option<&DateTime>); @@ -94,67 +87,6 @@ impl Item { } -#[derive(Clone, Debug, PartialEq, Hash)] -pub struct ItemId { - content: Url, -} -impl ItemId{ - /// Generate a random ItemId. - pub fn random(parent_calendar: &CalendarId) -> Self { - let random = uuid::Uuid::new_v4().to_hyphenated().to_string(); - let u = parent_calendar.join(&random).unwrap(/* this cannot panic since we've just created a string that is a valid URL */); - Self { content:u } - } - - pub fn as_url(&self) -> &Url { - &self.content - } -} -impl From for ItemId { - fn from(url: Url) -> Self { - Self { content: url } - } -} -impl From<&Resource> for ItemId { - fn from(resource: &Resource) -> Self { - Self { content: resource.url().clone() } - } -} -impl FromStr for ItemId { - type Err = url::ParseError; - fn from_str(s: &str) -> Result { - let u: Url = s.parse()?; - Ok(Self::from(u)) - } -} - -impl Eq for ItemId {} -impl Display for ItemId { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "{}", self.content) - } -} - -/// Used to support serde -impl Serialize for ItemId { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(self.content.as_str()) - } -} -/// Used to support serde -impl<'de> Deserialize<'de> for ItemId { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let u = Url::deserialize(deserializer)?; - Ok(ItemId{ content: u }) - } -} - /// A VersionTag is basically a CalDAV `ctag` or `etag`. Whenever it changes, this means the data has changed. diff --git a/src/provider/mod.rs b/src/provider/mod.rs index 6bb9a5e..2e9fb84 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -6,10 +6,11 @@ use std::error::Error; use std::collections::HashSet; use std::marker::PhantomData; use std::sync::{Arc, Mutex}; +use url::Url; use crate::traits::{BaseCalendar, CalDavSource, DavCalendar}; use crate::traits::CompleteCalendar; -use crate::item::{ItemId, SyncStatus}; +use crate::item::SyncStatus; use crate::calendar::CalendarId; pub mod sync_progress; @@ -400,7 +401,7 @@ where } - async fn item_name(cal: &T, id: &ItemId) -> String { + async fn item_name(cal: &T, id: &Url) -> String { cal.get_item_by_id(id).await.map(|item| item.name()).unwrap_or_default().to_string() } diff --git a/src/task.rs b/src/task.rs index 91e180a..4da9d93 100644 --- a/src/task.rs +++ b/src/task.rs @@ -4,10 +4,11 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; use chrono::{DateTime, Utc}; use ical::property::Property; +use url::Url; -use crate::item::ItemId; use crate::item::SyncStatus; use crate::calendar::CalendarId; +use crate::utils::random_url; /// RFC5545 defines the completion as several optional fields, yet some combinations make no sense. /// This enum provides an API that forbids such impossible combinations. @@ -33,10 +34,11 @@ impl CompletionStatus { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Task { /// The task URL - id: ItemId, + url: Url, /// Persistent, globally unique identifier for the calendar component - /// The [RFC](https://tools.ietf.org/html/rfc5545#page-117) recommends concatenating a timestamp with the server's domain name, but UUID are even better + /// The [RFC](https://tools.ietf.org/html/rfc5545#page-117) recommends concatenating a timestamp with the server's domain name. + /// UUID are even better so we'll generate them, but we have to support tasks from the server, that may have any arbitrary strings here. uid: String, /// The sync status of this item @@ -65,8 +67,8 @@ pub struct Task { impl Task { /// Create a brand new Task that is not on a server yet. /// This will pick a new (random) task ID. - pub fn new(name: String, completed: bool, parent_calendar_id: &CalendarId) -> Self { - let new_item_id = ItemId::random(parent_calendar_id); + pub fn new(name: String, completed: bool, parent_calendar_url: &CalendarId) -> Self { + let new_url = random_url(parent_calendar_url); let new_sync_status = SyncStatus::NotSynced; let new_uid = Uuid::new_v4().to_hyphenated().to_string(); let new_creation_date = Some(Utc::now()); @@ -76,18 +78,18 @@ impl Task { } else { CompletionStatus::Uncompleted }; let ical_prod_id = crate::ical::default_prod_id(); let extra_parameters = Vec::new(); - Self::new_with_parameters(name, new_uid, new_item_id, new_completion_status, new_sync_status, new_creation_date, new_last_modified, ical_prod_id, extra_parameters) + Self::new_with_parameters(name, new_uid, new_url, new_completion_status, new_sync_status, new_creation_date, new_last_modified, ical_prod_id, extra_parameters) } /// Create a new Task instance, that may be synced on the server already - pub fn new_with_parameters(name: String, uid: String, id: ItemId, + pub fn new_with_parameters(name: String, uid: String, new_url: Url, completion_status: CompletionStatus, sync_status: SyncStatus, creation_date: Option>, last_modified: DateTime, ical_prod_id: String, extra_parameters: Vec, ) -> Self { Self { - id, + url: new_url, uid, name, completion_status, @@ -99,7 +101,7 @@ impl Task { } } - pub fn id(&self) -> &ItemId { &self.id } + pub fn url(&self) -> &Url { &self.url } pub fn uid(&self) -> &str { &self.uid } pub fn name(&self) -> &str { &self.name } pub fn completed(&self) -> bool { self.completion_status.is_completed() } @@ -112,7 +114,8 @@ impl Task { #[cfg(any(test, feature = "integration_tests"))] pub fn has_same_observable_content_as(&self, other: &Task) -> bool { - self.id == other.id + self.url == other.url + && self.uid == other.uid && self.name == other.name // sync status must be the same variant, but we ignore its embedded version tag && std::mem::discriminant(&self.sync_status) == std::mem::discriminant(&other.sync_status) diff --git a/src/traits.rs b/src/traits.rs index 5dc1bd4..66c1b43 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -6,10 +6,10 @@ use std::sync::{Arc, Mutex}; use async_trait::async_trait; use csscolorparser::Color; +use url::Url; use crate::item::SyncStatus; use crate::item::Item; -use crate::item::ItemId; use crate::item::VersionTag; use crate::calendar::CalendarId; use crate::calendar::SupportedComponents; @@ -40,8 +40,8 @@ pub trait BaseCalendar { /// Returns the calendar name fn name(&self) -> &str; - /// Returns the calendar unique ID - fn id(&self) -> &CalendarId; + /// Returns the calendar URL + fn url(&self) -> &Url; /// Returns the supported kinds of components for this calendar fn supported_components(&self) -> crate::calendar::SupportedComponents; @@ -79,16 +79,16 @@ pub trait DavCalendar : BaseCalendar { fn new(name: String, resource: Resource, supported_components: SupportedComponents, color: Option) -> Self; /// Get the IDs and the version tags of every item in this calendar - async fn get_item_version_tags(&self) -> Result, Box>; + async fn get_item_version_tags(&self) -> Result, Box>; /// Returns a particular item - async fn get_item_by_id(&self, id: &ItemId) -> Result, Box>; + async fn get_item_by_id(&self, id: &Url) -> Result, Box>; /// Delete an item - async fn delete_item(&mut self, item_id: &ItemId) -> Result<(), Box>; + async fn delete_item(&mut self, item_id: &Url) -> Result<(), Box>; /// Get the IDs of all current items in this calendar - async fn get_item_ids(&self) -> Result, Box> { + async fn get_item_ids(&self) -> Result, Box> { let items = self.get_item_version_tags().await?; Ok(items.iter() .map(|(id, _tag)| id.clone()) @@ -111,22 +111,22 @@ pub trait CompleteCalendar : BaseCalendar { fn new(name: String, id: CalendarId, supported_components: SupportedComponents, color: Option) -> Self; /// Get the IDs of all current items in this calendar - async fn get_item_ids(&self) -> Result, Box>; + async fn get_item_ids(&self) -> Result, Box>; /// Returns all items that this calendar contains - async fn get_items(&self) -> Result, Box>; + async fn get_items(&self) -> Result, Box>; /// Returns a particular item - async fn get_item_by_id<'a>(&'a self, id: &ItemId) -> Option<&'a Item>; + async fn get_item_by_id<'a>(&'a self, id: &Url) -> Option<&'a Item>; /// Returns a particular item - async fn get_item_by_id_mut<'a>(&'a mut self, id: &ItemId) -> Option<&'a mut Item>; + async fn get_item_by_id_mut<'a>(&'a mut self, id: &Url) -> Option<&'a mut Item>; /// Mark an item for deletion. /// This is required so that the upcoming sync will know it should also also delete this task from the server /// (and then call [`CompleteCalendar::immediately_delete_item`] once it has been successfully deleted on the server) - async fn mark_for_deletion(&mut self, item_id: &ItemId) -> Result<(), Box>; + async fn mark_for_deletion(&mut self, item_id: &Url) -> Result<(), Box>; /// Immediately remove an item. See [`CompleteCalendar::mark_for_deletion`] - async fn immediately_delete_item(&mut self, item_id: &ItemId) -> Result<(), Box>; + async fn immediately_delete_item(&mut self, item_id: &Url) -> Result<(), Box>; } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 9aba3a5..68f1c69 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -6,6 +6,7 @@ use std::hash::Hash; use std::io::{stdin, stdout, Read, Write}; use minidom::Element; +use url::Url; use crate::traits::CompleteCalendar; use crate::traits::DavCalendar; @@ -107,7 +108,7 @@ pub fn print_task(item: &Item) { SyncStatus::LocallyModified(_) => "~", SyncStatus::LocallyDeleted(_) => "x", }; - println!(" {}{} {}\t{}", completion, sync, task.name(), task.id()); + println!(" {}{} {}\t{}", completion, sync, task.name(), task.url()); }, _ => return, } @@ -148,3 +149,10 @@ pub fn pause() { stdout.flush().unwrap(); stdin().read_exact(&mut [0]).unwrap(); } + + +/// Generate a random URL with a given prefix +pub fn random_url(parent_calendar: &Url) -> Url { + let random = uuid::Uuid::new_v4().to_hyphenated().to_string(); + parent_calendar.join(&random).unwrap(/* this cannot panic since we've just created a string that is a valid URL */) +} diff --git a/tests/scenarii.rs b/tests/scenarii.rs index 5888997..8987c8b 100644 --- a/tests/scenarii.rs +++ b/tests/scenarii.rs @@ -11,6 +11,7 @@ use std::path::PathBuf; use std::sync::{Arc, Mutex}; use std::error::Error; +use url::Url; use chrono::Utc; @@ -22,13 +23,13 @@ use kitchen_fridge::traits::CompleteCalendar; use kitchen_fridge::traits::DavCalendar; use kitchen_fridge::cache::Cache; use kitchen_fridge::Item; -use kitchen_fridge::item::ItemId; use kitchen_fridge::item::SyncStatus; use kitchen_fridge::Task; use kitchen_fridge::task::CompletionStatus; use kitchen_fridge::calendar::cached_calendar::CachedCalendar; use kitchen_fridge::provider::Provider; use kitchen_fridge::mock_behaviour::MockBehaviour; +use kitchen_fridge::utils::random_url; pub enum LocatedState { /// Item does not exist yet or does not exist anymore @@ -62,7 +63,7 @@ pub enum ChangeToApply { pub struct ItemScenario { - id: ItemId, + url: Url, initial_state: LocatedState, local_changes_to_apply: Vec, remote_changes_to_apply: Vec, @@ -93,7 +94,7 @@ pub fn scenarii_basic() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&first_cal), + url: random_url(&first_cal), initial_state: LocatedState::BothSynced( ItemState{ calendar: first_cal.clone(), name: String::from("Task A"), @@ -111,7 +112,7 @@ pub fn scenarii_basic() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&first_cal), + url: random_url(&first_cal), initial_state: LocatedState::BothSynced( ItemState{ calendar: first_cal.clone(), name: String::from("Task B"), @@ -125,7 +126,7 @@ pub fn scenarii_basic() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&first_cal), + url: random_url(&first_cal), initial_state: LocatedState::BothSynced( ItemState{ calendar: first_cal.clone(), name: String::from("Task C"), @@ -139,7 +140,7 @@ pub fn scenarii_basic() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&first_cal), + url: random_url(&first_cal), initial_state: LocatedState::BothSynced( ItemState{ calendar: first_cal.clone(), name: String::from("Task D"), @@ -157,7 +158,7 @@ pub fn scenarii_basic() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&first_cal), + url: random_url(&first_cal), initial_state: LocatedState::BothSynced( ItemState{ calendar: first_cal.clone(), name: String::from("Task E"), @@ -175,7 +176,7 @@ pub fn scenarii_basic() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&first_cal), + url: random_url(&first_cal), initial_state: LocatedState::BothSynced( ItemState{ calendar: first_cal.clone(), name: String::from("Task F"), @@ -194,7 +195,7 @@ pub fn scenarii_basic() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&second_cal), + url: random_url(&second_cal), initial_state: LocatedState::BothSynced( ItemState{ calendar: second_cal.clone(), name: String::from("Task G"), @@ -212,7 +213,7 @@ pub fn scenarii_basic() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&second_cal), + url: random_url(&second_cal), initial_state: LocatedState::BothSynced( ItemState{ calendar: second_cal.clone(), name: String::from("Task H"), @@ -230,7 +231,7 @@ pub fn scenarii_basic() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&second_cal), + url: random_url(&second_cal), initial_state: LocatedState::BothSynced( ItemState{ calendar: second_cal.clone(), name: String::from("Task I"), @@ -249,7 +250,7 @@ pub fn scenarii_basic() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&second_cal), + url: random_url(&second_cal), initial_state: LocatedState::BothSynced( ItemState{ calendar: second_cal.clone(), name: String::from("Task J"), @@ -263,7 +264,7 @@ pub fn scenarii_basic() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&second_cal), + url: random_url(&second_cal), initial_state: LocatedState::BothSynced( ItemState{ calendar: second_cal.clone(), name: String::from("Task K"), @@ -281,7 +282,7 @@ pub fn scenarii_basic() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&second_cal), + url: random_url(&second_cal), initial_state: LocatedState::BothSynced( ItemState{ calendar: second_cal.clone(), name: String::from("Task L"), @@ -295,7 +296,7 @@ pub fn scenarii_basic() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&second_cal), + url: random_url(&second_cal), initial_state: LocatedState::BothSynced( ItemState{ calendar: second_cal.clone(), name: String::from("Task M"), @@ -313,7 +314,7 @@ pub fn scenarii_basic() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&third_cal), + url: random_url(&third_cal), initial_state: LocatedState::BothSynced( ItemState{ calendar: third_cal.clone(), name: String::from("Task N"), @@ -331,7 +332,7 @@ pub fn scenarii_basic() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&third_cal), + url: random_url(&third_cal), initial_state: LocatedState::BothSynced( ItemState{ calendar: third_cal.clone(), name: String::from("Task O"), @@ -347,10 +348,10 @@ pub fn scenarii_basic() -> Vec { } ); - let id_p = ItemId::random(&third_cal); + let url_p = random_url(&third_cal); tasks.push( ItemScenario { - id: id_p.clone(), + url: url_p.clone(), initial_state: LocatedState::BothSynced( ItemState{ calendar: third_cal.clone(), name: String::from("Task P"), @@ -369,16 +370,16 @@ pub fn scenarii_basic() -> Vec { } ); - let id_q = ItemId::random(&third_cal); + let url_q = random_url(&third_cal); tasks.push( ItemScenario { - id: id_q.clone(), + url: url_q.clone(), initial_state: LocatedState::None, local_changes_to_apply: Vec::new(), remote_changes_to_apply: vec![ChangeToApply::Create(third_cal.clone(), Item::Task( Task::new_with_parameters( String::from("Task Q, created on the server"), - id_q.to_string(), id_q, + url_q.to_string(), url_q, CompletionStatus::Uncompleted, SyncStatus::random_synced(), Some(Utc::now()), Utc::now(), "prod_id".to_string(), Vec::new() ) ))], @@ -390,15 +391,15 @@ pub fn scenarii_basic() -> Vec { } ); - let id_r = ItemId::random(&third_cal); + let url_r = random_url(&third_cal); tasks.push( ItemScenario { - id: id_r.clone(), + url: url_r.clone(), initial_state: LocatedState::None, local_changes_to_apply: vec![ChangeToApply::Create(third_cal.clone(), Item::Task( Task::new_with_parameters( String::from("Task R, created locally"), - id_r.to_string(), id_r, + url_r.to_string(), url_r, CompletionStatus::Uncompleted, SyncStatus::NotSynced, Some(Utc::now()), Utc::now(), "prod_id".to_string(), Vec::new() ) ))], @@ -423,7 +424,7 @@ pub fn scenarii_first_sync_to_local() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&cal1), + url: random_url(&cal1), initial_state: LocatedState::Remote( ItemState{ calendar: cal1.clone(), name: String::from("Task A1"), @@ -441,7 +442,7 @@ pub fn scenarii_first_sync_to_local() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&cal2), + url: random_url(&cal2), initial_state: LocatedState::Remote( ItemState{ calendar: cal2.clone(), name: String::from("Task A2"), @@ -459,7 +460,7 @@ pub fn scenarii_first_sync_to_local() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&cal1), + url: random_url(&cal1), initial_state: LocatedState::Remote( ItemState{ calendar: cal1.clone(), name: String::from("Task B1"), @@ -487,7 +488,7 @@ pub fn scenarii_first_sync_to_server() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&cal3), + url: random_url(&cal3), initial_state: LocatedState::Local( ItemState{ calendar: cal3.clone(), name: String::from("Task A3"), @@ -505,7 +506,7 @@ pub fn scenarii_first_sync_to_server() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&cal4), + url: random_url(&cal4), initial_state: LocatedState::Local( ItemState{ calendar: cal4.clone(), name: String::from("Task A4"), @@ -523,7 +524,7 @@ pub fn scenarii_first_sync_to_server() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&cal3), + url: random_url(&cal3), initial_state: LocatedState::Local( ItemState{ calendar: cal3.clone(), name: String::from("Task B3"), @@ -551,7 +552,7 @@ pub fn scenarii_transient_task() -> Vec { tasks.push( ItemScenario { - id: ItemId::random(&cal), + url: random_url(&cal), initial_state: LocatedState::Local( ItemState{ calendar: cal.clone(), name: String::from("A task, so that the calendar actually exists"), @@ -567,16 +568,16 @@ pub fn scenarii_transient_task() -> Vec { } ); - let id_transient = ItemId::random(&cal); + let url_transient = random_url(&cal); tasks.push( ItemScenario { - id: id_transient.clone(), + url: url_transient.clone(), initial_state: LocatedState::None, local_changes_to_apply: vec![ ChangeToApply::Create(cal, Item::Task( Task::new_with_parameters( String::from("A transient task that will be deleted before the sync"), - id_transient.to_string(), id_transient, + url_transient.to_string(), url_transient, CompletionStatus::Uncompleted, SyncStatus::NotSynced, Some(Utc::now()), Utc::now(), "prod_id".to_string(), Vec::new() ) @@ -637,8 +638,8 @@ async fn populate_test_provider(scenarii: &[ItemScenario], mock_behaviour: Arc(source: &S, calendar_id: Option, item_id: &ItemId, change: &ChangeToApply, is_remote: bool) -> CalendarId +async fn apply_change(source: &S, calendar_id: Option, item_url: &Url, change: &ChangeToApply, is_remote: bool) -> CalendarId where S: CalDavSource, C: CompleteCalendar + DavCalendar, // in this test, we're using a calendar that mocks both kinds { match calendar_id { Some(cal) => { - apply_changes_on_an_existing_item(source, &cal, item_id, change, is_remote).await; + apply_changes_on_an_existing_item(source, &cal, item_url, change, is_remote).await; cal }, None => { @@ -723,14 +724,14 @@ where } } -async fn apply_changes_on_an_existing_item(source: &S, calendar_id: &CalendarId, item_id: &ItemId, change: &ChangeToApply, is_remote: bool) +async fn apply_changes_on_an_existing_item(source: &S, calendar_id: &CalendarId, item_url: &Url, change: &ChangeToApply, is_remote: bool) where S: CalDavSource, C: CompleteCalendar + DavCalendar, // in this test, we're using a calendar that mocks both kinds { let cal = source.get_calendar(calendar_id).await.unwrap(); let mut cal = cal.lock().unwrap(); - let task = cal.get_item_by_id_mut(item_id).await.unwrap().unwrap_task_mut(); + let task = cal.get_item_by_id_mut(item_url).await.unwrap().unwrap_task_mut(); match change { ChangeToApply::Rename(new_name) => { @@ -753,8 +754,8 @@ where }, ChangeToApply::Remove => { match is_remote { - false => cal.mark_for_deletion(item_id).await.unwrap(), - true => cal.delete_item(item_id).await.unwrap(), + false => cal.mark_for_deletion(item_url).await.unwrap(), + true => cal.delete_item(item_url).await.unwrap(), }; }, ChangeToApply::Create(_calendar_id, _item) => { From 0075ebfbdb1bfdd916cfb6d4d98037dc4768c253 Mon Sep 17 00:00:00 2001 From: daladim Date: Tue, 16 Nov 2021 00:10:47 +0100 Subject: [PATCH 2/2] More ID -> URL renaming --- examples/provider-sync.rs | 30 +++--- src/cache.rs | 38 +++---- src/calendar/cached_calendar.rs | 72 ++++++------- src/calendar/mod.rs | 3 - src/calendar/remote_calendar.rs | 20 ++-- src/client.rs | 17 ++- src/ical/builder.rs | 4 +- src/mock_behaviour.rs | 8 +- src/provider/mod.rs | 183 ++++++++++++++++---------------- src/task.rs | 3 +- src/traits.rs | 31 +++--- src/utils/mod.rs | 17 ++- tests/scenarii.rs | 65 ++++++------ tests/sync.rs | 6 +- 14 files changed, 244 insertions(+), 253 deletions(-) diff --git a/examples/provider-sync.rs b/examples/provider-sync.rs index a93dbf5..666c0b1 100644 --- a/examples/provider-sync.rs +++ b/examples/provider-sync.rs @@ -6,7 +6,7 @@ use chrono::{Utc}; use url::Url; use kitchen_fridge::{client::Client, traits::CalDavSource}; -use kitchen_fridge::calendar::{CalendarId, SupportedComponents}; +use kitchen_fridge::calendar::SupportedComponents; use kitchen_fridge::Item; use kitchen_fridge::Task; use kitchen_fridge::task::CompletionStatus; @@ -81,27 +81,27 @@ async fn add_items_and_sync_again(provider: &mut CalDavProvider) pause(); // Create a new calendar... - let new_calendar_id: CalendarId = EXAMPLE_CREATED_CALENDAR_URL.parse().unwrap(); + let new_calendar_url: Url = EXAMPLE_CREATED_CALENDAR_URL.parse().unwrap(); let new_calendar_name = "A brave new calendar".to_string(); if let Err(_err) = provider.local_mut() - .create_calendar(new_calendar_id.clone(), new_calendar_name.clone(), SupportedComponents::TODO, None) + .create_calendar(new_calendar_url.clone(), new_calendar_name.clone(), SupportedComponents::TODO, None) .await { println!("Unable to add calendar, maybe it exists already. We're not adding it after all."); } // ...and add a task in it let new_name = "This is a new task in a new calendar"; - let new_task = Task::new(String::from(new_name), true, &new_calendar_id); - provider.local().get_calendar(&new_calendar_id).await.unwrap() + let new_task = Task::new(String::from(new_name), true, &new_calendar_url); + provider.local().get_calendar(&new_calendar_url).await.unwrap() .lock().unwrap().add_item(Item::Task(new_task)).await.unwrap(); // Also create a task in a previously existing calendar - let changed_calendar_id: CalendarId = EXAMPLE_EXISTING_CALENDAR_URL.parse().unwrap(); + let changed_calendar_url: Url = EXAMPLE_EXISTING_CALENDAR_URL.parse().unwrap(); let new_task_name = "This is a new task we're adding as an example, with ÜTF-8 characters"; - let new_task = Task::new(String::from(new_task_name), false, &changed_calendar_id); + let new_task = Task::new(String::from(new_task_name), false, &changed_calendar_url); let new_url = new_task.url().clone(); - provider.local().get_calendar(&changed_calendar_id).await.unwrap() + provider.local().get_calendar(&changed_calendar_url).await.unwrap() .lock().unwrap().add_item(Item::Task(new_task)).await.unwrap(); @@ -112,20 +112,20 @@ async fn add_items_and_sync_again(provider: &mut CalDavProvider) } provider.local().save_to_folder().unwrap(); - complete_item_and_sync_again(provider, &changed_calendar_id, &new_url).await; + complete_item_and_sync_again(provider, &changed_calendar_url, &new_url).await; } async fn complete_item_and_sync_again( provider: &mut CalDavProvider, - changed_calendar_id: &CalendarId, + changed_calendar_url: &Url, url_to_complete: &Url) { println!("\nNow, we'll mark this last task as completed, and run the sync again."); pause(); let completion_status = CompletionStatus::Completed(Some(Utc::now())); - provider.local().get_calendar(changed_calendar_id).await.unwrap() - .lock().unwrap().get_item_by_id_mut(url_to_complete).await.unwrap() + provider.local().get_calendar(changed_calendar_url).await.unwrap() + .lock().unwrap().get_item_by_url_mut(url_to_complete).await.unwrap() .unwrap_task_mut() .set_completion_status(completion_status); @@ -136,19 +136,19 @@ async fn complete_item_and_sync_again( } provider.local().save_to_folder().unwrap(); - remove_items_and_sync_again(provider, changed_calendar_id, url_to_complete).await; + remove_items_and_sync_again(provider, changed_calendar_url, url_to_complete).await; } async fn remove_items_and_sync_again( provider: &mut CalDavProvider, - changed_calendar_id: &CalendarId, + changed_calendar_url: &Url, id_to_remove: &Url) { println!("\nNow, we'll delete this last task, and run the sync again."); pause(); // Remove the task we had created - provider.local().get_calendar(changed_calendar_id).await.unwrap() + provider.local().get_calendar(changed_calendar_url).await.unwrap() .lock().unwrap() .mark_for_deletion(id_to_remove).await.unwrap(); diff --git a/src/cache.rs b/src/cache.rs index 926ec43..062fb85 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -10,12 +10,12 @@ use std::ffi::OsStr; use serde::{Deserialize, Serialize}; use async_trait::async_trait; use csscolorparser::Color; +use url::Url; use crate::traits::CalDavSource; use crate::traits::BaseCalendar; use crate::traits::CompleteCalendar; use crate::calendar::cached_calendar::CachedCalendar; -use crate::calendar::CalendarId; use crate::calendar::SupportedComponents; #[cfg(feature = "local_calendar_mocks_remote_calendars")] @@ -41,7 +41,7 @@ pub struct Cache { #[derive(Default, Debug, Serialize, Deserialize)] struct CachedData { #[serde(skip)] - calendars: HashMap>>, + calendars: HashMap>>, } impl Cache { @@ -131,8 +131,8 @@ impl Cache { serde_json::to_writer(file, &self.data)?; // Save each calendar - for (cal_id, cal_mutex) in &self.data.calendars { - let file_name = sanitize_filename::sanitize(cal_id.as_str()) + ".cal"; + for (cal_url, cal_mutex) in &self.data.calendars { + let file_name = sanitize_filename::sanitize(cal_url.as_str()) + ".cal"; let cal_file = folder.join(file_name); let file = std::fs::File::create(&cal_file)?; let cal = cal_mutex.lock().unwrap(); @@ -156,10 +156,10 @@ impl Cache { return Ok(false); } - for (calendar_id, cal_l) in calendars_l { - log::debug!("Comparing calendars {}", calendar_id); + for (calendar_url, cal_l) in calendars_l { + log::debug!("Comparing calendars {}", calendar_url); let cal_l = cal_l.lock().unwrap(); - let cal_r = match calendars_r.get(&calendar_id) { + let cal_r = match calendars_r.get(&calendar_url) { Some(c) => c.lock().unwrap(), None => return Err("should not happen, we've just tested keys are the same".into()), }; @@ -185,38 +185,38 @@ impl Drop for Cache { impl Cache { /// The non-async version of [`crate::traits::CalDavSource::get_calendars`] - pub fn get_calendars_sync(&self) -> Result>>, Box> { + pub fn get_calendars_sync(&self) -> Result>>, Box> { #[cfg(feature = "local_calendar_mocks_remote_calendars")] self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_get_calendars())?; Ok(self.data.calendars.iter() - .map(|(id, cal)| (id.clone(), cal.clone())) + .map(|(url, cal)| (url.clone(), cal.clone())) .collect() ) } /// The non-async version of [`crate::traits::CalDavSource::get_calendar`] - pub fn get_calendar_sync(&self, id: &CalendarId) -> Option>> { - self.data.calendars.get(id).map(|arc| arc.clone()) + pub fn get_calendar_sync(&self, url: &Url) -> Option>> { + self.data.calendars.get(url).map(|arc| arc.clone()) } } #[async_trait] impl CalDavSource for Cache { - async fn get_calendars(&self) -> Result>>, Box> { + async fn get_calendars(&self) -> Result>>, Box> { self.get_calendars_sync() } - async fn get_calendar(&self, id: &CalendarId) -> Option>> { - self.get_calendar_sync(id) + async fn get_calendar(&self, url: &Url) -> Option>> { + self.get_calendar_sync(url) } - async fn create_calendar(&mut self, id: CalendarId, name: String, supported_components: SupportedComponents, color: Option) -> Result>, Box> { - log::debug!("Inserting local calendar {}", id); + async fn create_calendar(&mut self, url: Url, name: String, supported_components: SupportedComponents, color: Option) -> Result>, Box> { + log::debug!("Inserting local calendar {}", url); #[cfg(feature = "local_calendar_mocks_remote_calendars")] self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_create_calendar())?; - let new_calendar = CachedCalendar::new(name, id.clone(), supported_components, color); + let new_calendar = CachedCalendar::new(name, url.clone(), supported_components, color); let arc = Arc::new(Mutex::new(new_calendar)); #[cfg(feature = "local_calendar_mocks_remote_calendars")] @@ -224,7 +224,7 @@ impl CalDavSource for Cache { arc.lock().unwrap().set_mock_behaviour(Some(Arc::clone(behaviour))); }; - match self.data.calendars.insert(id, arc.clone()) { + match self.data.calendars.insert(url, arc.clone()) { Some(_) => Err("Attempt to insert calendar failed: there is alredy such a calendar.".into()), None => Ok(arc), } @@ -293,7 +293,7 @@ mod tests { let cache_path = PathBuf::from(String::from("test_cache/sanity_tests")); let mut cache = populate_cache(&cache_path).await; - // We should not be able to add a second calendar with the same id + // We should not be able to add a second calendar with the same URL let second_addition_same_calendar = cache.create_calendar( Url::parse("https://caldav.com/shopping").unwrap(), "My shopping list".to_string(), diff --git a/src/calendar/cached_calendar.rs b/src/calendar/cached_calendar.rs index aa14db8..502cdbd 100644 --- a/src/calendar/cached_calendar.rs +++ b/src/calendar/cached_calendar.rs @@ -8,7 +8,7 @@ use url::Url; use crate::item::SyncStatus; use crate::traits::{BaseCalendar, CompleteCalendar}; -use crate::calendar::{CalendarId, SupportedComponents}; +use crate::calendar::SupportedComponents; use crate::Item; #[cfg(feature = "local_calendar_mocks_remote_calendars")] @@ -102,13 +102,13 @@ impl CachedCalendar { log::debug!("Different keys for items"); return Ok(false); } - for (id_l, item_l) in items_l { - let item_r = match items_r.get(&id_l) { + for (url_l, item_l) in items_l { + let item_r = match items_r.get(&url_l) { Some(c) => c, None => return Err("should not happen, we've just tested keys are the same".into()), }; if item_l.has_same_observable_content_as(&item_r) == false { - log::debug!("Different items for id {}:", id_l); + log::debug!("Different items for URL {}:", url_l); log::debug!("{:#?}", item_l); log::debug!("{:#?}", item_r); return Ok(false); @@ -118,8 +118,8 @@ impl CachedCalendar { Ok(true) } - /// The non-async version of [`Self::get_item_ids`] - pub fn get_item_ids_sync(&self) -> Result, Box> { + /// The non-async version of [`Self::get_item_urls`] + pub fn get_item_urls_sync(&self) -> Result, Box> { Ok(self.items.iter() .map(|(url, _)| url.clone()) .collect() @@ -134,14 +134,14 @@ impl CachedCalendar { ) } - /// The non-async version of [`Self::get_item_by_id`] - pub fn get_item_by_id_sync<'a>(&'a self, id: &Url) -> Option<&'a Item> { - self.items.get(id) + /// The non-async version of [`Self::get_item_by_url`] + pub fn get_item_by_url_sync<'a>(&'a self, url: &Url) -> Option<&'a Item> { + self.items.get(url) } - /// The non-async version of [`Self::get_item_by_id_mut`] - pub fn get_item_by_id_mut_sync<'a>(&'a mut self, id: &Url) -> Option<&'a mut Item> { - self.items.get_mut(id) + /// The non-async version of [`Self::get_item_by_url_mut`] + pub fn get_item_by_url_mut_sync<'a>(&'a mut self, url: &Url) -> Option<&'a mut Item> { + self.items.get_mut(url) } /// The non-async version of [`Self::add_item`] @@ -169,8 +169,8 @@ impl CachedCalendar { } /// The non-async version of [`Self::mark_for_deletion`] - pub fn mark_for_deletion_sync(&mut self, item_id: &Url) -> Result<(), Box> { - match self.items.get_mut(item_id) { + pub fn mark_for_deletion_sync(&mut self, item_url: &Url) -> Result<(), Box> { + match self.items.get_mut(item_url) { None => Err("no item for this key".into()), Some(item) => { match item.sync_status() { @@ -188,7 +188,7 @@ impl CachedCalendar { }, SyncStatus::NotSynced => { // This was never synced to the server, we can safely delete it as soon as now - self.items.remove(item_id); + self.items.remove(item_url); }, }; Ok(()) @@ -197,9 +197,9 @@ impl CachedCalendar { } /// The non-async version of [`Self::immediately_delete_item`] - pub fn immediately_delete_item_sync(&mut self, item_id: &Url) -> Result<(), Box> { - match self.items.remove(item_id) { - None => Err(format!("Item {} is absent from this calendar", item_id).into()), + pub fn immediately_delete_item_sync(&mut self, item_url: &Url) -> Result<(), Box> { + match self.items.remove(item_url) { + None => Err(format!("Item {} is absent from this calendar", item_url).into()), Some(_) => Ok(()) } } @@ -236,7 +236,7 @@ impl BaseCalendar for CachedCalendar { #[async_trait] impl CompleteCalendar for CachedCalendar { - fn new(name: String, url: CalendarId, supported_components: SupportedComponents, color: Option) -> Self { + fn new(name: String, url: Url, supported_components: SupportedComponents, color: Option) -> Self { Self { name, url, supported_components, color, #[cfg(feature = "local_calendar_mocks_remote_calendars")] @@ -245,28 +245,28 @@ impl CompleteCalendar for CachedCalendar { } } - async fn get_item_ids(&self) -> Result, Box> { - self.get_item_ids_sync() + async fn get_item_urls(&self) -> Result, Box> { + self.get_item_urls_sync() } async fn get_items(&self) -> Result, Box> { self.get_items_sync() } - async fn get_item_by_id<'a>(&'a self, id: &Url) -> Option<&'a Item> { - self.get_item_by_id_sync(id) + async fn get_item_by_url<'a>(&'a self, url: &Url) -> Option<&'a Item> { + self.get_item_by_url_sync(url) } - async fn get_item_by_id_mut<'a>(&'a mut self, id: &Url) -> Option<&'a mut Item> { - self.get_item_by_id_mut_sync(id) + async fn get_item_by_url_mut<'a>(&'a mut self, url: &Url) -> Option<&'a mut Item> { + self.get_item_by_url_mut_sync(url) } - async fn mark_for_deletion(&mut self, item_id: &Url) -> Result<(), Box> { - self.mark_for_deletion_sync(item_id) + async fn mark_for_deletion(&mut self, item_url: &Url) -> Result<(), Box> { + self.mark_for_deletion_sync(item_url) } - async fn immediately_delete_item(&mut self, item_id: &Url) -> Result<(), Box> { - self.immediately_delete_item_sync(item_id) + async fn immediately_delete_item(&mut self, item_url: &Url) -> Result<(), Box> { + self.immediately_delete_item_sync(item_url) } } @@ -294,30 +294,30 @@ impl DavCalendar for CachedCalendar { let mut result = HashMap::new(); - for (id, item) in self.items.iter() { + for (url, item) in self.items.iter() { let vt = match item.sync_status() { SyncStatus::Synced(vt) => vt.clone(), _ => { panic!("Mock calendars must contain only SyncStatus::Synced. Got {:?}", item); } }; - result.insert(id.clone(), vt); + result.insert(url.clone(), vt); } Ok(result) } - async fn get_item_by_id(&self, id: &Url) -> Result, Box> { + async fn get_item_by_url(&self, url: &Url) -> Result, Box> { #[cfg(feature = "local_calendar_mocks_remote_calendars")] - self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_get_item_by_id())?; + self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_get_item_by_url())?; - Ok(self.items.get(id).cloned()) + Ok(self.items.get(url).cloned()) } - async fn delete_item(&mut self, item_id: &Url) -> Result<(), Box> { + async fn delete_item(&mut self, item_url: &Url) -> Result<(), Box> { #[cfg(feature = "local_calendar_mocks_remote_calendars")] self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_delete_item())?; - self.immediately_delete_item(item_id).await + self.immediately_delete_item(item_url).await } } diff --git a/src/calendar/mod.rs b/src/calendar/mod.rs index 8ff675b..9ccce9b 100644 --- a/src/calendar/mod.rs +++ b/src/calendar/mod.rs @@ -77,6 +77,3 @@ impl Default for SearchFilter { SearchFilter::All } } - - -pub type CalendarId = url::Url; diff --git a/src/calendar/remote_calendar.rs b/src/calendar/remote_calendar.rs index 050c78a..f4f1ab1 100644 --- a/src/calendar/remote_calendar.rs +++ b/src/calendar/remote_calendar.rs @@ -138,7 +138,7 @@ impl DavCalendar for RemoteCalendar { for response in responses { let item_url = crate::utils::find_elem(&response, "href") .map(|elem| self.resource.combine(&elem.text())); - let item_id = match item_url { + let item_url = match item_url { None => { log::warn!("Unable to extract HREF"); continue; @@ -150,7 +150,7 @@ impl DavCalendar for RemoteCalendar { let version_tag = match crate::utils::find_elem(&response, "getetag") { None => { - log::warn!("Unable to extract ETAG for item {}, ignoring it", item_id); + log::warn!("Unable to extract ETAG for item {}, ignoring it", item_url); continue; }, Some(etag) => { @@ -158,7 +158,7 @@ impl DavCalendar for RemoteCalendar { } }; - items.insert(item_id.clone(), version_tag); + items.insert(item_url.clone(), version_tag); } // Note: the mutex cannot be locked during this whole async function, but it can safely be re-entrant (this will just waste an unnecessary request) @@ -166,9 +166,9 @@ impl DavCalendar for RemoteCalendar { Ok(items) } - async fn get_item_by_id(&self, id: &Url) -> Result, Box> { + async fn get_item_by_url(&self, url: &Url) -> Result, Box> { let res = reqwest::Client::new() - .get(id.clone()) + .get(url.clone()) .header(CONTENT_TYPE, "text/calendar") .basic_auth(self.resource.username(), Some(self.resource.password())) .send() @@ -182,18 +182,18 @@ impl DavCalendar for RemoteCalendar { // This is supposed to be cached let version_tags = self.get_item_version_tags().await?; - let vt = match version_tags.get(id) { - None => return Err(format!("Inconsistent data: {} has no version tag", id).into()), + let vt = match version_tags.get(url) { + None => return Err(format!("Inconsistent data: {} has no version tag", url).into()), Some(vt) => vt, }; - let item = crate::ical::parse(&text, id.clone(), SyncStatus::Synced(vt.clone()))?; + let item = crate::ical::parse(&text, url.clone(), SyncStatus::Synced(vt.clone()))?; Ok(Some(item)) } - async fn delete_item(&mut self, item_id: &Url) -> Result<(), Box> { + async fn delete_item(&mut self, item_url: &Url) -> Result<(), Box> { let del_response = reqwest::Client::new() - .delete(item_id.clone()) + .delete(item_url.clone()) .basic_auth(self.resource.username(), Some(self.resource.password())) .send() .await?; diff --git a/src/client.rs b/src/client.rs index 242a1a1..a4d843e 100644 --- a/src/client.rs +++ b/src/client.rs @@ -15,7 +15,6 @@ use csscolorparser::Color; use crate::resource::Resource; use crate::utils::{find_elem, find_elems}; use crate::calendar::remote_calendar::RemoteCalendar; -use crate::calendar::CalendarId; use crate::calendar::SupportedComponents; use crate::traits::CalDavSource; use crate::traits::BaseCalendar; @@ -113,7 +112,7 @@ pub struct Client { struct CachedReplies { principal: Option, calendar_home_set: Option, - calendars: Option>>>, + calendars: Option>>>, } impl Client { @@ -228,7 +227,7 @@ impl Client { #[async_trait] impl CalDavSource for Client { - async fn get_calendars(&self) -> Result>>, Box> { + async fn get_calendars(&self) -> Result>>, Box> { self.populate_calendars().await?; match &self.cached_replies.lock().unwrap().calendars { @@ -239,7 +238,7 @@ impl CalDavSource for Client { }; } - async fn get_calendar(&self, id: &CalendarId) -> Option>> { + async fn get_calendar(&self, url: &Url) -> Option>> { if let Err(err) = self.populate_calendars().await { log::warn!("Unable to fetch calendars: {}", err); return None; @@ -248,17 +247,17 @@ impl CalDavSource for Client { self.cached_replies.lock().unwrap() .calendars .as_ref() - .and_then(|cals| cals.get(id)) + .and_then(|cals| cals.get(url)) .map(|cal| cal.clone()) } - async fn create_calendar(&mut self, id: CalendarId, name: String, supported_components: SupportedComponents, color: Option) -> Result>, Box> { + async fn create_calendar(&mut self, url: Url, name: String, supported_components: SupportedComponents, color: Option) -> Result>, Box> { self.populate_calendars().await?; match self.cached_replies.lock().unwrap().calendars.as_ref() { None => return Err("No calendars have been fetched".into()), Some(cals) => { - if cals.contains_key(&id) { + if cals.contains_key(&url) { return Err("This calendar already exists".into()); } }, @@ -267,7 +266,7 @@ impl CalDavSource for Client { let creation_body = calendar_body(name, supported_components); let response = reqwest::Client::new() - .request(Method::from_bytes(b"MKCALENDAR").unwrap(), id.clone()) + .request(Method::from_bytes(b"MKCALENDAR").unwrap(), url.clone()) .header(CONTENT_TYPE, "application/xml") .basic_auth(self.resource.username(), Some(self.resource.password())) .body(creation_body) @@ -279,7 +278,7 @@ impl CalDavSource for Client { return Err(format!("Unexpected HTTP status code. Expected CREATED, got {}", status.as_u16()).into()); } - self.get_calendar(&id).await.ok_or(format!("Unable to insert calendar {:?}", id).into()) + self.get_calendar(&url).await.ok_or(format!("Unable to insert calendar {:?}", url).into()) } } diff --git a/src/ical/builder.rs b/src/ical/builder.rs index 05550e9..3e4840e 100644 --- a/src/ical/builder.rs +++ b/src/ical/builder.rs @@ -130,12 +130,12 @@ mod tests { } fn build_task(completed: bool) -> (String, String, String) { - let cal_id = "http://my.calend.ar/id".parse().unwrap(); + let cal_url = "http://my.calend.ar/id".parse().unwrap(); let now = Utc::now(); let s_now = format_date_time(&now); let task = Item::Task(Task::new( - String::from("This is a task with ÜTF-8 characters"), completed, &cal_id + String::from("This is a task with ÜTF-8 characters"), completed, &cal_url )); let ical = build_from(&task).unwrap(); diff --git a/src/mock_behaviour.rs b/src/mock_behaviour.rs index c40c368..c5c8bd1 100644 --- a/src/mock_behaviour.rs +++ b/src/mock_behaviour.rs @@ -22,7 +22,7 @@ pub struct MockBehaviour { // From the DavCalendar trait pub get_item_version_tags_behaviour: (u32, u32), - pub get_item_by_id_behaviour: (u32, u32), + pub get_item_by_url_behaviour: (u32, u32), pub delete_item_behaviour: (u32, u32), } @@ -41,7 +41,7 @@ impl MockBehaviour { 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), + get_item_by_url_behaviour: (0, n_fails), delete_item_behaviour: (0, n_fails), } } @@ -84,9 +84,9 @@ impl MockBehaviour { if self.is_suspended { return Ok(()) } decrement(&mut self.get_item_version_tags_behaviour, "get_item_version_tags") } - pub fn can_get_item_by_id(&mut self) -> Result<(), Box> { + pub fn can_get_item_by_url(&mut self) -> Result<(), Box> { if self.is_suspended { return Ok(()) } - decrement(&mut self.get_item_by_id_behaviour, "get_item_by_id") + decrement(&mut self.get_item_by_url_behaviour, "get_item_by_url") } pub fn can_delete_item(&mut self) -> Result<(), Box> { if self.is_suspended { return Ok(()) } diff --git a/src/provider/mod.rs b/src/provider/mod.rs index 2e9fb84..aaca96f 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -11,7 +11,6 @@ use url::Url; use crate::traits::{BaseCalendar, CalDavSource, DavCalendar}; use crate::traits::CompleteCalendar; use crate::item::SyncStatus; -use crate::calendar::CalendarId; pub mod sync_progress; use sync_progress::SyncProgress; @@ -103,39 +102,39 @@ where // Sync every remote calendar let cals_remote = self.remote.get_calendars().await?; - for (cal_id, cal_remote) in cals_remote { - let counterpart = match self.get_or_insert_local_counterpart_calendar(&cal_id, cal_remote.clone()).await { + for (cal_url, cal_remote) in cals_remote { + let counterpart = match self.get_or_insert_local_counterpart_calendar(&cal_url, cal_remote.clone()).await { Err(err) => { - progress.warn(&format!("Unable to get or insert local counterpart calendar for {} ({}). Skipping this time", cal_id, err)); + progress.warn(&format!("Unable to get or insert local counterpart calendar for {} ({}). Skipping this time", cal_url, err)); continue; }, Ok(arc) => arc, }; if let Err(err) = Self::sync_calendar_pair(counterpart, cal_remote, progress).await { - progress.warn(&format!("Unable to sync calendar {}: {}, skipping this time.", cal_id, err)); + progress.warn(&format!("Unable to sync calendar {}: {}, skipping this time.", cal_url, err)); continue; } - handled_calendars.insert(cal_id); + handled_calendars.insert(cal_url); } // Sync every local calendar that would not be in the remote yet let cals_local = self.local.get_calendars().await?; - for (cal_id, cal_local) in cals_local { - if handled_calendars.contains(&cal_id) { + for (cal_url, cal_local) in cals_local { + if handled_calendars.contains(&cal_url) { continue; } - let counterpart = match self.get_or_insert_remote_counterpart_calendar(&cal_id, cal_local.clone()).await { + let counterpart = match self.get_or_insert_remote_counterpart_calendar(&cal_url, cal_local.clone()).await { Err(err) => { - progress.warn(&format!("Unable to get or insert remote counterpart calendar for {} ({}). Skipping this time", cal_id, err)); + progress.warn(&format!("Unable to get or insert remote counterpart calendar for {} ({}). Skipping this time", cal_url, err)); continue; }, Ok(arc) => arc, }; if let Err(err) = Self::sync_calendar_pair(cal_local, counterpart, progress).await { - progress.warn(&format!("Unable to sync calendar {}: {}, skipping this time.", cal_id, err)); + progress.warn(&format!("Unable to sync calendar {}: {}, skipping this time.", cal_url, err)); continue; } } @@ -146,11 +145,11 @@ where } - async fn get_or_insert_local_counterpart_calendar(&mut self, cal_id: &CalendarId, needle: Arc>) -> Result>, Box> { - get_or_insert_counterpart_calendar("local", &mut self.local, cal_id, needle).await + async fn get_or_insert_local_counterpart_calendar(&mut self, cal_url: &Url, needle: Arc>) -> Result>, Box> { + get_or_insert_counterpart_calendar("local", &mut self.local, cal_url, needle).await } - async fn get_or_insert_remote_counterpart_calendar(&mut self, cal_id: &CalendarId, needle: Arc>) -> Result>, Box> { - get_or_insert_counterpart_calendar("remote", &mut self.remote, cal_id, needle).await + async fn get_or_insert_remote_counterpart_calendar(&mut self, cal_url: &Url, needle: Arc>) -> Result>, Box> { + get_or_insert_counterpart_calendar("remote", &mut self.remote, cal_url, needle).await } @@ -180,52 +179,52 @@ where details: format!("{} remote items", remote_items.len()), }); - let mut local_items_to_handle = cal_local.get_item_ids().await?; - for (id, remote_tag) in remote_items { - progress.trace(&format!("***** Considering remote item {}...", id)); - match cal_local.get_item_by_id(&id).await { + let mut local_items_to_handle = cal_local.get_item_urls().await?; + for (url, remote_tag) in remote_items { + progress.trace(&format!("***** Considering remote item {}...", url)); + match cal_local.get_item_by_url(&url).await { None => { // This was created on the remote - progress.debug(&format!("* {} is a remote addition", id)); - remote_additions.insert(id); + progress.debug(&format!("* {} is a remote addition", url)); + remote_additions.insert(url); }, Some(local_item) => { - if local_items_to_handle.remove(&id) == false { - progress.error(&format!("Inconsistent state: missing task {} from the local tasks", id)); + if local_items_to_handle.remove(&url) == false { + progress.error(&format!("Inconsistent state: missing task {} from the local tasks", url)); } match local_item.sync_status() { SyncStatus::NotSynced => { - progress.error(&format!("ID reuse between remote and local sources ({}). Ignoring this item in the sync", id)); + progress.error(&format!("URL reuse between remote and local sources ({}). Ignoring this item in the sync", url)); continue; }, SyncStatus::Synced(local_tag) => { if &remote_tag != local_tag { // This has been modified on the remote - progress.debug(&format!("* {} is a remote change", id)); - remote_changes.insert(id); + progress.debug(&format!("* {} is a remote change", url)); + remote_changes.insert(url); } }, SyncStatus::LocallyModified(local_tag) => { if &remote_tag == local_tag { // This has been changed locally - progress.debug(&format!("* {} is a local change", id)); - local_changes.insert(id); + progress.debug(&format!("* {} is a local change", url)); + local_changes.insert(url); } else { - progress.info(&format!("Conflict: task {} has been modified in both sources. Using the remote version.", id)); - progress.debug(&format!("* {} is considered a remote change", id)); - remote_changes.insert(id); + progress.info(&format!("Conflict: task {} has been modified in both sources. Using the remote version.", url)); + progress.debug(&format!("* {} is considered a remote change", url)); + remote_changes.insert(url); } }, SyncStatus::LocallyDeleted(local_tag) => { if &remote_tag == local_tag { // This has been locally deleted - progress.debug(&format!("* {} is a local deletion", id)); - local_del.insert(id); + progress.debug(&format!("* {} is a local deletion", url)); + local_del.insert(url); } else { - progress.info(&format!("Conflict: task {} has been locally deleted and remotely modified. Reverting to the remote version.", id)); - progress.debug(&format!("* {} is a considered a remote change", id)); - remote_changes.insert(id); + progress.info(&format!("Conflict: task {} has been locally deleted and remotely modified. Reverting to the remote version.", url)); + progress.debug(&format!("* {} is a considered a remote change", url)); + remote_changes.insert(url); } }, } @@ -234,11 +233,11 @@ where } // Also iterate on the local tasks that are not on the remote - for id in local_items_to_handle { - progress.trace(&format!("##### Considering local item {}...", id)); - let local_item = match cal_local.get_item_by_id(&id).await { + for url in local_items_to_handle { + progress.trace(&format!("##### Considering local item {}...", url)); + let local_item = match cal_local.get_item_by_url(&url).await { None => { - progress.error(&format!("Inconsistent state: missing task {} from the local tasks", id)); + progress.error(&format!("Inconsistent state: missing task {} from the local tasks", url)); continue; }, Some(item) => item, @@ -247,22 +246,22 @@ where match local_item.sync_status() { SyncStatus::Synced(_) => { // This item has been removed from the remote - progress.debug(&format!("# {} is a deletion from the server", id)); - remote_del.insert(id); + progress.debug(&format!("# {} is a deletion from the server", url)); + remote_del.insert(url); }, SyncStatus::NotSynced => { // This item has just been locally created - progress.debug(&format!("# {} has been locally created", id)); - local_additions.insert(id); + progress.debug(&format!("# {} has been locally created", url)); + local_additions.insert(url); }, SyncStatus::LocallyDeleted(_) => { // This item has been deleted from both sources - progress.debug(&format!("# {} has been deleted from both sources", id)); - remote_del.insert(id); + progress.debug(&format!("# {} has been deleted from both sources", url)); + remote_del.insert(url); }, SyncStatus::LocallyModified(_) => { - progress.info(&format!("Conflict: item {} has been deleted from the server and locally modified. Deleting the local copy", id)); - remote_del.insert(id); + progress.info(&format!("Conflict: item {} has been deleted from the server and locally modified. Deleting the local copy", url)); + remote_del.insert(url); }, } } @@ -270,80 +269,80 @@ where // Step 2 - commit changes progress.trace("Committing changes..."); - for id_del in local_del { - progress.debug(&format!("> Pushing local deletion {} to the server", id_del)); + for url_del in local_del { + progress.debug(&format!("> Pushing local deletion {} to the server", url_del)); progress.feedback(SyncEvent::InProgress{ calendar: cal_name.clone(), - details: Self::item_name(&cal_local, &id_del).await, + details: Self::item_name(&cal_local, &url_del).await, }); - match cal_remote.delete_item(&id_del).await { + match cal_remote.delete_item(&url_del).await { Err(err) => { - progress.warn(&format!("Unable to delete remote item {}: {}", id_del, err)); + progress.warn(&format!("Unable to delete remote item {}: {}", url_del, err)); }, Ok(()) => { // Change the local copy from "marked to deletion" to "actually deleted" - if let Err(err) = cal_local.immediately_delete_item(&id_del).await { - progress.error(&format!("Unable to permanently delete local item {}: {}", id_del, err)); + if let Err(err) = cal_local.immediately_delete_item(&url_del).await { + progress.error(&format!("Unable to permanently delete local item {}: {}", url_del, err)); } }, } } - for id_del in remote_del { - progress.debug(&format!("> Applying remote deletion {} locally", id_del)); + for url_del in remote_del { + progress.debug(&format!("> Applying remote deletion {} locally", url_del)); progress.feedback(SyncEvent::InProgress{ calendar: cal_name.clone(), - details: Self::item_name(&cal_local, &id_del).await, + details: Self::item_name(&cal_local, &url_del).await, }); - if let Err(err) = cal_local.immediately_delete_item(&id_del).await { - progress.warn(&format!("Unable to delete local item {}: {}", id_del, err)); + if let Err(err) = cal_local.immediately_delete_item(&url_del).await { + progress.warn(&format!("Unable to delete local item {}: {}", url_del, err)); } } - for id_add in remote_additions { - progress.debug(&format!("> Applying remote addition {} locally", id_add)); + for url_add in remote_additions { + progress.debug(&format!("> Applying remote addition {} locally", url_add)); progress.feedback(SyncEvent::InProgress{ calendar: cal_name.clone(), - details: Self::item_name(&cal_local, &id_add).await, + details: Self::item_name(&cal_local, &url_add).await, }); - match cal_remote.get_item_by_id(&id_add).await { + match cal_remote.get_item_by_url(&url_add).await { Err(err) => { - progress.warn(&format!("Unable to get remote item {}: {}. Skipping it.", id_add, err)); + progress.warn(&format!("Unable to get remote item {}: {}. Skipping it.", url_add, err)); continue; }, Ok(item) => match item { None => { - progress.error(&format!("Inconsistency: new item {} has vanished from the remote end", id_add)); + progress.error(&format!("Inconsistency: new item {} has vanished from the remote end", url_add)); continue; }, Some(new_item) => { if let Err(err) = cal_local.add_item(new_item.clone()).await { - progress.error(&format!("Not able to add item {} to local calendar: {}", id_add, err)); + progress.error(&format!("Not able to add item {} to local calendar: {}", url_add, err)); } }, }, } } - for id_change in remote_changes { - progress.debug(&format!("> Applying remote change {} locally", id_change)); + for url_change in remote_changes { + progress.debug(&format!("> Applying remote change {} locally", url_change)); progress.feedback(SyncEvent::InProgress{ calendar: cal_name.clone(), - details: Self::item_name(&cal_local, &id_change).await, + details: Self::item_name(&cal_local, &url_change).await, }); - match cal_remote.get_item_by_id(&id_change).await { + match cal_remote.get_item_by_url(&url_change).await { Err(err) => { - progress.warn(&format!("Unable to get remote item {}: {}. Skipping it", id_change, err)); + progress.warn(&format!("Unable to get remote item {}: {}. Skipping it", url_change, err)); continue; }, Ok(item) => match item { None => { - progress.error(&format!("Inconsistency: modified item {} has vanished from the remote end", id_change)); + progress.error(&format!("Inconsistency: modified item {} has vanished from the remote end", url_change)); continue; }, Some(item) => { if let Err(err) = cal_local.update_item(item.clone()).await { - progress.error(&format!("Unable to update item {} in local calendar: {}", id_change, err)); + progress.error(&format!("Unable to update item {} in local calendar: {}", url_change, err)); } }, } @@ -351,20 +350,20 @@ where } - for id_add in local_additions { - progress.debug(&format!("> Pushing local addition {} to the server", id_add)); + for url_add in local_additions { + progress.debug(&format!("> Pushing local addition {} to the server", url_add)); progress.feedback(SyncEvent::InProgress{ calendar: cal_name.clone(), - details: Self::item_name(&cal_local, &id_add).await, + details: Self::item_name(&cal_local, &url_add).await, }); - match cal_local.get_item_by_id_mut(&id_add).await { + match cal_local.get_item_by_url_mut(&url_add).await { None => { - progress.error(&format!("Inconsistency: created item {} has been marked for upload but is locally missing", id_add)); + progress.error(&format!("Inconsistency: created item {} has been marked for upload but is locally missing", url_add)); continue; }, Some(item) => { match cal_remote.add_item(item.clone()).await { - Err(err) => progress.error(&format!("Unable to add item {} to remote calendar: {}", id_add, err)), + Err(err) => progress.error(&format!("Unable to add item {} to remote calendar: {}", url_add, err)), Ok(new_ss) => { // Update local sync status item.set_sync_status(new_ss); @@ -374,20 +373,20 @@ where }; } - for id_change in local_changes { - progress.debug(&format!("> Pushing local change {} to the server", id_change)); + for url_change in local_changes { + progress.debug(&format!("> Pushing local change {} to the server", url_change)); progress.feedback(SyncEvent::InProgress{ calendar: cal_name.clone(), - details: Self::item_name(&cal_local, &id_change).await, + details: Self::item_name(&cal_local, &url_change).await, }); - match cal_local.get_item_by_id_mut(&id_change).await { + match cal_local.get_item_by_url_mut(&url_change).await { None => { - progress.error(&format!("Inconsistency: modified item {} has been marked for upload but is locally missing", id_change)); + progress.error(&format!("Inconsistency: modified item {} has been marked for upload but is locally missing", url_change)); continue; }, Some(item) => { match cal_remote.update_item(item.clone()).await { - Err(err) => progress.error(&format!("Unable to update item {} in remote calendar: {}", id_change, err)), + Err(err) => progress.error(&format!("Unable to update item {} in remote calendar: {}", url_change, err)), Ok(new_ss) => { // Update local sync status item.set_sync_status(new_ss); @@ -401,14 +400,14 @@ where } - async fn item_name(cal: &T, id: &Url) -> String { - cal.get_item_by_id(id).await.map(|item| item.name()).unwrap_or_default().to_string() + async fn item_name(cal: &T, url: &Url) -> String { + cal.get_item_by_url(url).await.map(|item| item.name()).unwrap_or_default().to_string() } } -async fn get_or_insert_counterpart_calendar(haystack_descr: &str, haystack: &mut H, cal_id: &CalendarId, needle: Arc>) +async fn get_or_insert_counterpart_calendar(haystack_descr: &str, haystack: &mut H, cal_url: &Url, needle: Arc>) -> Result>, Box> where H: CalDavSource, @@ -416,18 +415,18 @@ where N: BaseCalendar, { loop { - if let Some(cal) = haystack.get_calendar(&cal_id).await { + if let Some(cal) = haystack.get_calendar(&cal_url).await { break Ok(cal); } // This calendar does not exist locally yet, let's add it - log::debug!("Adding a {} calendar {}", haystack_descr, cal_id); + log::debug!("Adding a {} calendar {}", haystack_descr, cal_url); let src = needle.lock().unwrap(); let name = src.name().to_string(); let supported_comps = src.supported_components(); let color = src.color(); if let Err(err) = haystack.create_calendar( - cal_id.clone(), + cal_url.clone(), name, supported_comps, color.cloned(), diff --git a/src/task.rs b/src/task.rs index 4da9d93..2545abb 100644 --- a/src/task.rs +++ b/src/task.rs @@ -7,7 +7,6 @@ use ical::property::Property; use url::Url; use crate::item::SyncStatus; -use crate::calendar::CalendarId; use crate::utils::random_url; /// RFC5545 defines the completion as several optional fields, yet some combinations make no sense. @@ -67,7 +66,7 @@ pub struct Task { impl Task { /// Create a brand new Task that is not on a server yet. /// This will pick a new (random) task ID. - pub fn new(name: String, completed: bool, parent_calendar_url: &CalendarId) -> Self { + pub fn new(name: String, completed: bool, parent_calendar_url: &Url) -> Self { let new_url = random_url(parent_calendar_url); let new_sync_status = SyncStatus::NotSynced; let new_uid = Uuid::new_v4().to_hyphenated().to_string(); diff --git a/src/traits.rs b/src/traits.rs index 66c1b43..e6d5f28 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -11,7 +11,6 @@ use url::Url; use crate::item::SyncStatus; use crate::item::Item; use crate::item::VersionTag; -use crate::calendar::CalendarId; use crate::calendar::SupportedComponents; use crate::resource::Resource; @@ -22,11 +21,11 @@ use crate::resource::Resource; pub trait CalDavSource { /// Returns the current calendars that this source contains /// This function may trigger an update (that can be a long process, or that can even fail, e.g. in case of a remote server) - async fn get_calendars(&self) -> Result>>, Box>; - /// Returns the calendar matching the ID - async fn get_calendar(&self, id: &CalendarId) -> Option>>; + async fn get_calendars(&self) -> Result>>, Box>; + /// Returns the calendar matching the URL + async fn get_calendar(&self, url: &Url) -> Option>>; /// Create a calendar if it did not exist, and return it - async fn create_calendar(&mut self, id: CalendarId, name: String, supported_components: SupportedComponents, color: Option) + async fn create_calendar(&mut self, url: Url, name: String, supported_components: SupportedComponents, color: Option) -> Result>, Box>; // Removing a calendar is not supported yet @@ -78,20 +77,20 @@ pub trait DavCalendar : BaseCalendar { /// Create a new calendar fn new(name: String, resource: Resource, supported_components: SupportedComponents, color: Option) -> Self; - /// Get the IDs and the version tags of every item in this calendar + /// Get the URLs 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(&self, id: &Url) -> Result, Box>; + async fn get_item_by_url(&self, url: &Url) -> Result, Box>; /// Delete an item - async fn delete_item(&mut self, item_id: &Url) -> Result<(), Box>; + async fn delete_item(&mut self, item_url: &Url) -> Result<(), Box>; - /// Get the IDs of all current items in this calendar - async fn get_item_ids(&self) -> Result, Box> { + /// Get the URLs of all current items in this calendar + async fn get_item_urls(&self) -> Result, Box> { let items = self.get_item_version_tags().await?; Ok(items.iter() - .map(|(id, _tag)| id.clone()) + .map(|(url, _tag)| url.clone()) .collect()) } @@ -108,19 +107,19 @@ pub trait DavCalendar : BaseCalendar { #[async_trait] pub trait CompleteCalendar : BaseCalendar { /// Create a new calendar - fn new(name: String, id: CalendarId, supported_components: SupportedComponents, color: Option) -> Self; + fn new(name: String, url: Url, supported_components: SupportedComponents, color: Option) -> Self; - /// Get the IDs of all current items in this calendar - async fn get_item_ids(&self) -> Result, Box>; + /// Get the URLs of all current items in this calendar + async fn get_item_urls(&self) -> Result, Box>; /// Returns all items that this calendar contains async fn get_items(&self) -> Result, Box>; /// Returns a particular item - async fn get_item_by_id<'a>(&'a self, id: &Url) -> Option<&'a Item>; + async fn get_item_by_url<'a>(&'a self, url: &Url) -> Option<&'a Item>; /// Returns a particular item - async fn get_item_by_id_mut<'a>(&'a mut self, id: &Url) -> Option<&'a mut Item>; + async fn get_item_by_url_mut<'a>(&'a mut self, url: &Url) -> Option<&'a mut Item>; /// Mark an item for deletion. /// This is required so that the upcoming sync will know it should also also delete this task from the server diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 68f1c69..8829265 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -10,7 +10,6 @@ use url::Url; use crate::traits::CompleteCalendar; use crate::traits::DavCalendar; -use crate::calendar::CalendarId; use crate::Item; use crate::item::SyncStatus; @@ -63,12 +62,12 @@ pub fn print_xml(element: &Element) { } /// A debug utility that pretty-prints calendars -pub async fn print_calendar_list(cals: &HashMap>>) +pub async fn print_calendar_list(cals: &HashMap>>) where C: CompleteCalendar, { - for (id, cal) in cals { - println!("CAL {} ({})", cal.lock().unwrap().name(), id); + for (url, cal) in cals { + println!("CAL {} ({})", cal.lock().unwrap().name(), url); match cal.lock().unwrap().get_items().await { Err(_err) => continue, Ok(map) => { @@ -81,17 +80,17 @@ where } /// A debug utility that pretty-prints calendars -pub async fn print_dav_calendar_list(cals: &HashMap>>) +pub async fn print_dav_calendar_list(cals: &HashMap>>) where C: DavCalendar, { - for (id, cal) in cals { - println!("CAL {} ({})", cal.lock().unwrap().name(), id); + for (url, cal) in cals { + println!("CAL {} ({})", cal.lock().unwrap().name(), url); match cal.lock().unwrap().get_item_version_tags().await { Err(_err) => continue, Ok(map) => { - for (id, version_tag) in map { - println!(" * {} (version {:?})", id, version_tag); + for (url, version_tag) in map { + println!(" * {} (version {:?})", url, version_tag); } }, } diff --git a/tests/scenarii.rs b/tests/scenarii.rs index 8987c8b..231af2a 100644 --- a/tests/scenarii.rs +++ b/tests/scenarii.rs @@ -15,7 +15,6 @@ use url::Url; use chrono::Utc; -use kitchen_fridge::calendar::CalendarId; use kitchen_fridge::calendar::SupportedComponents; use kitchen_fridge::traits::CalDavSource; use kitchen_fridge::traits::BaseCalendar; @@ -45,7 +44,7 @@ pub enum LocatedState { pub struct ItemState { // TODO: if/when this crate supports Events as well, we could add such events here /// The calendar it is in - calendar: CalendarId, + calendar: Url, /// Its name name: String, /// Its completion status @@ -55,10 +54,10 @@ pub struct ItemState { pub enum ChangeToApply { Rename(String), SetCompletion(bool), - Create(CalendarId, Item), + Create(Url, Item), /// "remove" means "mark for deletion" in the local calendar, or "immediately delete" on the remote calendar Remove, - // ChangeCalendar(CalendarId) is useless, as long as changing a calendar is implemented as "delete in one calendar and re-create it in another one" + // ChangeCalendar(Url) is useless, as long as changing a calendar is implemented as "delete in one calendar and re-create it in another one" } @@ -88,9 +87,9 @@ pub struct ItemScenario { pub fn scenarii_basic() -> Vec { let mut tasks = Vec::new(); - let first_cal = CalendarId::from("https://some.calend.ar/calendar-1/".parse().unwrap()); - let second_cal = CalendarId::from("https://some.calend.ar/calendar-2/".parse().unwrap()); - let third_cal = CalendarId::from("https://some.calend.ar/calendar-3/".parse().unwrap()); + let first_cal = Url::from("https://some.calend.ar/calendar-1/".parse().unwrap()); + let second_cal = Url::from("https://some.calend.ar/calendar-2/".parse().unwrap()); + let third_cal = Url::from("https://some.calend.ar/calendar-3/".parse().unwrap()); tasks.push( ItemScenario { @@ -419,8 +418,8 @@ pub fn scenarii_basic() -> Vec { pub fn scenarii_first_sync_to_local() -> Vec { let mut tasks = Vec::new(); - let cal1 = CalendarId::from("https://some.calend.ar/first/".parse().unwrap()); - let cal2 = CalendarId::from("https://some.calend.ar/second/".parse().unwrap()); + let cal1 = Url::from("https://some.calend.ar/first/".parse().unwrap()); + let cal2 = Url::from("https://some.calend.ar/second/".parse().unwrap()); tasks.push( ItemScenario { @@ -483,8 +482,8 @@ pub fn scenarii_first_sync_to_local() -> Vec { pub fn scenarii_first_sync_to_server() -> Vec { let mut tasks = Vec::new(); - let cal3 = CalendarId::from("https://some.calend.ar/third/".parse().unwrap()); - let cal4 = CalendarId::from("https://some.calend.ar/fourth/".parse().unwrap()); + let cal3 = Url::from("https://some.calend.ar/third/".parse().unwrap()); + let cal4 = Url::from("https://some.calend.ar/fourth/".parse().unwrap()); tasks.push( ItemScenario { @@ -548,7 +547,7 @@ pub fn scenarii_first_sync_to_server() -> Vec { pub fn scenarii_transient_task() -> Vec { let mut tasks = Vec::new(); - let cal = CalendarId::from("https://some.calend.ar/transient/".parse().unwrap()); + let cal = Url::from("https://some.calend.ar/transient/".parse().unwrap()); tasks.push( ItemScenario { @@ -668,37 +667,37 @@ async fn populate_test_provider(scenarii: &[ItemScenario], mock_behaviour: Arc, scenarii: &[ItemScenario]) { // Apply changes to each item for item in scenarii { - let initial_calendar_id = match &item.initial_state { + let initial_calendar_url = match &item.initial_state { LocatedState::None => None, LocatedState::Local(state) => Some(state.calendar.clone()), LocatedState::Remote(state) => Some(state.calendar.clone()), LocatedState::BothSynced(state) => Some(state.calendar.clone()), }; - let mut calendar_id = initial_calendar_id.clone(); + let mut calendar_url = initial_calendar_url.clone(); for local_change in &item.local_changes_to_apply { - calendar_id = Some(apply_change(provider.local(), calendar_id, &item.url, local_change, false).await); + calendar_url = Some(apply_change(provider.local(), calendar_url, &item.url, local_change, false).await); } - let mut calendar_id = initial_calendar_id; + let mut calendar_url = initial_calendar_url; for remote_change in &item.remote_changes_to_apply { - calendar_id = Some(apply_change(provider.remote(), calendar_id, &item.url, remote_change, true).await); + calendar_url = Some(apply_change(provider.remote(), calendar_url, &item.url, remote_change, true).await); } } } -async fn get_or_insert_calendar(source: &mut Cache, id: &CalendarId) +async fn get_or_insert_calendar(source: &mut Cache, url: &Url) -> Result>, Box> { - match source.get_calendar(id).await { + match source.get_calendar(url).await { Some(cal) => Ok(cal), None => { - let new_name = format!("Test calendar for ID {}", id); + let new_name = format!("Test calendar for URL {}", url); let supported_components = SupportedComponents::TODO; let color = csscolorparser::parse("#ff8000"); // TODO: we should rather have specific colors, depending on the calendars source.create_calendar( - id.clone(), + url.clone(), new_name.to_string(), supported_components, None, @@ -707,13 +706,13 @@ async fn get_or_insert_calendar(source: &mut Cache, id: &CalendarId) } } -/// Apply a single change on a given source, and returns the calendar ID that was modified -async fn apply_change(source: &S, calendar_id: Option, item_url: &Url, change: &ChangeToApply, is_remote: bool) -> CalendarId +/// Apply a single change on a given source, and returns the calendar URL that was modified +async fn apply_change(source: &S, calendar_url: Option, item_url: &Url, change: &ChangeToApply, is_remote: bool) -> Url where S: CalDavSource, C: CompleteCalendar + DavCalendar, // in this test, we're using a calendar that mocks both kinds { - match calendar_id { + match calendar_url { Some(cal) => { apply_changes_on_an_existing_item(source, &cal, item_url, change, is_remote).await; cal @@ -724,14 +723,14 @@ where } } -async fn apply_changes_on_an_existing_item(source: &S, calendar_id: &CalendarId, item_url: &Url, change: &ChangeToApply, is_remote: bool) +async fn apply_changes_on_an_existing_item(source: &S, calendar_url: &Url, item_url: &Url, change: &ChangeToApply, is_remote: bool) where S: CalDavSource, C: CompleteCalendar + DavCalendar, // in this test, we're using a calendar that mocks both kinds { - let cal = source.get_calendar(calendar_id).await.unwrap(); + let cal = source.get_calendar(calendar_url).await.unwrap(); let mut cal = cal.lock().unwrap(); - let task = cal.get_item_by_id_mut(item_url).await.unwrap().unwrap_task_mut(); + let task = cal.get_item_by_url_mut(item_url).await.unwrap().unwrap_task_mut(); match change { ChangeToApply::Rename(new_name) => { @@ -758,14 +757,14 @@ where true => cal.delete_item(item_url).await.unwrap(), }; }, - ChangeToApply::Create(_calendar_id, _item) => { + ChangeToApply::Create(_calendar_url, _item) => { panic!("This function only handles already existing items"); }, } } -/// Create an item, and returns the calendar ID it was inserted in -async fn create_test_item(source: &S, change: &ChangeToApply) -> CalendarId +/// Create an item, and returns the URL of the calendar it was inserted in +async fn create_test_item(source: &S, change: &ChangeToApply) -> Url where S: CalDavSource, C: CompleteCalendar + DavCalendar, // in this test, we're using a calendar that mocks both kinds @@ -776,10 +775,10 @@ where ChangeToApply::Remove => { panic!("This function only creates items that do not exist yet"); } - ChangeToApply::Create(calendar_id, item) => { - let cal = source.get_calendar(calendar_id).await.unwrap(); + ChangeToApply::Create(calendar_url, item) => { + let cal = source.get_calendar(calendar_url).await.unwrap(); cal.lock().unwrap().add_item(item.clone()).await.unwrap(); - calendar_id.clone() + calendar_url.clone() }, } } diff --git a/tests/sync.rs b/tests/sync.rs index 59536d7..37e92ac 100644 --- a/tests/sync.rs +++ b/tests/sync.rs @@ -124,7 +124,7 @@ impl TestFlavour { Self { scenarii: scenarii::scenarii_basic(), mock_behaviour: Arc::new(Mutex::new(MockBehaviour{ - get_item_by_id_behaviour: (3,2), + get_item_by_url_behaviour: (3,2), ..MockBehaviour::default() })), } @@ -145,7 +145,7 @@ impl TestFlavour { scenarii: scenarii::scenarii_basic(), mock_behaviour: Arc::new(Mutex::new(MockBehaviour{ add_item_behaviour: (2,3), - get_item_by_id_behaviour: (1,4), + get_item_by_url_behaviour: (1,4), ..MockBehaviour::default() })), } @@ -183,7 +183,7 @@ impl TestFlavour { delete_item_behaviour: (1,1), create_calendar_behaviour: (1,4), get_item_version_tags_behaviour: (3,1), - get_item_by_id_behaviour: (0,41), + get_item_by_url_behaviour: (0,41), ..MockBehaviour::default() })), }