diff --git a/Cargo.lock b/Cargo.lock index 1fb1ccc..ea4129b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,6 +103,16 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" +[[package]] +name = "csscolorparser" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2fb3bd93ef32553e3d5b9f8020028f41ac64ff8a230033d5d548b8222d21fbe" +dependencies = [ + "phf", + "serde", +] + [[package]] name = "encoding_rs" version = "0.8.28" @@ -196,6 +206,17 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.2" @@ -204,7 +225,7 @@ checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.10.2+wasi-snapshot-preview1", ] [[package]] @@ -382,6 +403,7 @@ dependencies = [ "async-trait", "bitflags", "chrono", + "csscolorparser", "env_logger", "ical", "ics", @@ -568,6 +590,50 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros", + "phf_shared", + "proc-macro-hack", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared", + "rand 0.7.3", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.0.5" @@ -612,6 +678,12 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + [[package]] name = "proc-macro2" version = "1.0.24" @@ -639,6 +711,20 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", + "rand_pcg", +] + [[package]] name = "rand" version = "0.8.3" @@ -646,9 +732,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ "libc", - "rand_chacha", - "rand_core", - "rand_hc", + "rand_chacha 0.3.0", + "rand_core 0.6.2", + "rand_hc 0.3.0", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] @@ -658,7 +754,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.2", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", ] [[package]] @@ -667,7 +772,16 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" dependencies = [ - "getrandom", + "getrandom 0.2.2", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", ] [[package]] @@ -676,7 +790,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" dependencies = [ - "rand_core", + "rand_core 0.6.2", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", ] [[package]] @@ -841,6 +964,12 @@ dependencies = [ "serde", ] +[[package]] +name = "siphasher" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27" + [[package]] name = "slab" version = "0.4.2" @@ -877,7 +1006,7 @@ checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ "cfg-if", "libc", - "rand", + "rand 0.8.3", "redox_syscall", "remove_dir_all", "winapi", @@ -1082,7 +1211,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom", + "getrandom 0.2.2", ] [[package]] @@ -1101,6 +1230,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index a2eebb6..baa9f39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,3 +26,4 @@ sanitize-filename = "0.3" ical = "0.7" ics = "0.5" chrono = { version = "0.4", features = ["serde"] } +csscolorparser = { version = "0.5", features = ["serde"] } diff --git a/examples/provider-sync.rs b/examples/provider-sync.rs index 28043b8..b8b5234 100644 --- a/examples/provider-sync.rs +++ b/examples/provider-sync.rs @@ -69,7 +69,7 @@ async fn add_items_and_sync_again(provider: &mut CalDavProvider) let new_calendar_id: CalendarId = 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) + .create_calendar(new_calendar_id.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."); } diff --git a/src/cache.rs b/src/cache.rs index 443de21..639cd64 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -9,6 +9,7 @@ use std::ffi::OsStr; use serde::{Deserialize, Serialize}; use async_trait::async_trait; +use csscolorparser::Color; use crate::traits::CalDavSource; use crate::traits::BaseCalendar; @@ -208,12 +209,12 @@ impl CalDavSource for Cache { self.get_calendar_sync(id) } - async fn create_calendar(&mut self, id: CalendarId, name: String, supported_components: SupportedComponents) -> Result>, Box> { + async fn create_calendar(&mut self, id: CalendarId, name: String, supported_components: SupportedComponents, color: Option) -> Result>, Box> { log::debug!("Inserting local calendar {}", id); #[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); + let new_calendar = CachedCalendar::new(name, id.clone(), supported_components, color); let arc = Arc::new(Mutex::new(new_calendar)); #[cfg(feature = "local_calendar_mocks_remote_calendars")] @@ -244,12 +245,14 @@ mod tests { Url::parse("https://caldav.com/shopping").unwrap(), "My shopping list".to_string(), SupportedComponents::TODO, + Some(csscolorparser::parse("lime").unwrap()), ).await.unwrap(); let bucket_list = cache.create_calendar( Url::parse("https://caldav.com/bucket-list").unwrap(), "My bucket list".to_string(), SupportedComponents::TODO, + Some(csscolorparser::parse("#ff8000").unwrap()), ).await.unwrap(); { @@ -288,11 +291,12 @@ 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 twice the same calendar + // We should not be able to add a second calendar with the same id let second_addition_same_calendar = cache.create_calendar( Url::parse("https://caldav.com/shopping").unwrap(), "My shopping list".to_string(), SupportedComponents::TODO, + None, ).await; assert!(second_addition_same_calendar.is_err()); } diff --git a/src/calendar/cached_calendar.rs b/src/calendar/cached_calendar.rs index 49958b2..ac74519 100644 --- a/src/calendar/cached_calendar.rs +++ b/src/calendar/cached_calendar.rs @@ -3,6 +3,7 @@ use std::error::Error; use serde::{Deserialize, Serialize}; use async_trait::async_trait; +use csscolorparser::Color; use crate::item::SyncStatus; use crate::traits::{BaseCalendar, CompleteCalendar}; @@ -24,6 +25,7 @@ pub struct CachedCalendar { name: String, id: CalendarId, supported_components: SupportedComponents, + color: Option, #[cfg(feature = "local_calendar_mocks_remote_calendars")] #[serde(skip)] mock_behaviour: Option>>, @@ -85,7 +87,9 @@ impl CachedCalendar { pub async fn has_same_observable_content_as(&self, other: &CachedCalendar) -> Result> { if self.name != other.name || self.id != other.id - || self.supported_components != other.supported_components { + || self.supported_components != other.supported_components + || self.color != other.color + { log::debug!("Calendar properties mismatch"); return Ok(false); } @@ -156,6 +160,10 @@ impl BaseCalendar for CachedCalendar { self.supported_components } + fn color(&self) -> Option<&Color> { + self.color.as_ref() + } + async fn add_item(&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()); @@ -181,9 +189,9 @@ impl BaseCalendar for CachedCalendar { #[async_trait] impl CompleteCalendar for CachedCalendar { - fn new(name: String, id: CalendarId, supported_components: SupportedComponents) -> Self { + fn new(name: String, id: CalendarId, supported_components: SupportedComponents, color: Option) -> Self { Self { - name, id, supported_components, + name, id, supported_components, color, #[cfg(feature = "local_calendar_mocks_remote_calendars")] mock_behaviour: None, items: HashMap::new(), @@ -253,8 +261,8 @@ use crate::{item::VersionTag, #[cfg(feature = "local_calendar_mocks_remote_calendars")] #[async_trait] impl DavCalendar for CachedCalendar { - fn new(name: String, resource: Resource, supported_components: SupportedComponents) -> Self { - crate::traits::CompleteCalendar::new(name, resource.url().clone(), supported_components) + fn new(name: String, resource: Resource, supported_components: SupportedComponents, color: Option) -> Self { + crate::traits::CompleteCalendar::new(name, resource.url().clone(), supported_components, color) } async fn get_item_version_tags(&self) -> Result, Box> { diff --git a/src/calendar/remote_calendar.rs b/src/calendar/remote_calendar.rs index c975105..14912d4 100644 --- a/src/calendar/remote_calendar.rs +++ b/src/calendar/remote_calendar.rs @@ -4,6 +4,7 @@ use std::sync::Mutex; use async_trait::async_trait; use reqwest::{header::CONTENT_TYPE, header::CONTENT_LENGTH}; +use csscolorparser::Color; use crate::traits::BaseCalendar; use crate::traits::DavCalendar; @@ -37,6 +38,7 @@ pub struct RemoteCalendar { name: String, resource: Resource, supported_components: SupportedComponents, + color: Option, cached_version_tags: Mutex>>, } @@ -48,6 +50,9 @@ impl BaseCalendar for RemoteCalendar { fn supported_components(&self) -> crate::calendar::SupportedComponents { self.supported_components } + fn color(&self) -> Option<&Color> { + self.color.as_ref() + } async fn add_item(&mut self, item: Item) -> Result> { let ical_text = crate::ical::build_from(&item)?; @@ -114,9 +119,9 @@ impl BaseCalendar for RemoteCalendar { #[async_trait] impl DavCalendar for RemoteCalendar { - fn new(name: String, resource: Resource, supported_components: SupportedComponents) -> Self { + fn new(name: String, resource: Resource, supported_components: SupportedComponents, color: Option) -> Self { Self { - name, resource, supported_components, + name, resource, supported_components, color, cached_version_tags: Mutex::new(None), } } diff --git a/src/client.rs b/src/client.rs index 2e4487f..112fdc0 100644 --- a/src/client.rs +++ b/src/client.rs @@ -10,6 +10,7 @@ use reqwest::{Method, StatusCode}; use reqwest::header::CONTENT_TYPE; use minidom::Element; use url::Url; +use csscolorparser::Color; use crate::resource::Resource; use crate::utils::{find_elem, find_elems}; @@ -42,6 +43,7 @@ static CAL_BODY: &str = r#" + @@ -205,7 +207,14 @@ impl Client { }, Ok(sc) => sc, }; - let this_calendar = RemoteCalendar::new(display_name, this_calendar_url, supported_components); + + let this_calendar_color = find_elem(&rep, "calendar-color") + .and_then(|col| { + col.texts().next() + .and_then(|t| csscolorparser::parse(t).ok()) + }); + + 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))); } @@ -243,7 +252,7 @@ impl CalDavSource for Client { .map(|cal| cal.clone()) } - async fn create_calendar(&mut self, id: CalendarId, name: String, supported_components: SupportedComponents) -> Result>, Box> { + async fn create_calendar(&mut self, id: CalendarId, name: String, supported_components: SupportedComponents, color: Option) -> Result>, Box> { self.populate_calendars().await?; match self.cached_replies.lock().unwrap().calendars.as_ref() { diff --git a/src/provider.rs b/src/provider.rs index 4c64ce3..aad647e 100644 --- a/src/provider.rs +++ b/src/provider.rs @@ -390,10 +390,12 @@ where 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(), name, supported_comps, + color.cloned(), ).await{ return Err(err); } diff --git a/src/traits.rs b/src/traits.rs index 20051fb..bd9adf1 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet}; use std::sync::{Arc, Mutex}; use async_trait::async_trait; +use csscolorparser::Color; use crate::item::SyncStatus; use crate::item::Item; @@ -21,7 +22,7 @@ pub trait CalDavSource { /// Returns the calendar matching the ID async fn get_calendar(&self, id: &CalendarId) -> 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) + async fn create_calendar(&mut self, id: CalendarId, name: String, supported_components: SupportedComponents, color: Option) -> Result>, Box>; // Removing a calendar is not supported yet @@ -39,6 +40,9 @@ pub trait BaseCalendar { /// Returns the supported kinds of components for this calendar fn supported_components(&self) -> crate::calendar::SupportedComponents; + /// Returns the user-defined color of this calendar + fn color(&self) -> Option<&Color>; + /// Add an item into this calendar, and return its new sync status. /// For local calendars, the sync status is not modified. /// For remote calendars, the sync status is updated by the server @@ -64,7 +68,7 @@ pub trait BaseCalendar { #[async_trait] pub trait DavCalendar : BaseCalendar { /// Create a new calendar - fn new(name: String, resource: Resource, supported_components: SupportedComponents) -> Self; + 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>; @@ -94,7 +98,7 @@ pub trait DavCalendar : BaseCalendar { #[async_trait] pub trait CompleteCalendar : BaseCalendar { /// Create a new calendar - fn new(name: String, id: CalendarId, supported_components: SupportedComponents) -> Self; + 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>; diff --git a/tests/caldav_client.rs b/tests/caldav_client.rs index 1f9dcc7..9c13531 100644 --- a/tests/caldav_client.rs +++ b/tests/caldav_client.rs @@ -50,14 +50,15 @@ async fn show_calendars() { } #[tokio::test] +#[ignore] async fn create_cal() { let _ = env_logger::builder().is_test(true).try_init(); let mut client = Client::new(URL, USERNAME, PASSWORD).unwrap(); let id: Url = kitchen_fridge::settings::EXAMPLE_CREATED_CALENDAR_URL.parse().unwrap(); - let name = "prout".into(); + let name = "a created calendar".into(); let supported_components = SupportedComponents::TODO; - client.create_calendar(id, name, supported_components).await.unwrap(); + client.create_calendar(id, name, supported_components, Some(csscolorparser::parse("gold").unwrap())).await.unwrap(); } #[tokio::test] diff --git a/tests/scenarii.rs b/tests/scenarii.rs index 5377b32..758481c 100644 --- a/tests/scenarii.rs +++ b/tests/scenarii.rs @@ -692,11 +692,13 @@ async fn get_or_insert_calendar(source: &mut Cache, id: &CalendarId) None => { let new_name = format!("Test calendar for ID {}", id); 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(), new_name.to_string(), supported_components, + None, ).await } }