From 8e1547750ef159ca19da6815493ec5dbec47c494 Mon Sep 17 00:00:00 2001 From: Matteo Settenvini Date: Thu, 28 Jul 2022 18:32:38 +0200 Subject: [PATCH] Publish cal events to server --- Cargo.lock | 47 ++++++++++++++++++++++++ Cargo.toml | 4 +++ src/commands/schedule_csv.rs | 70 ++++++++++++++++++++++++++---------- 3 files changed, 102 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5844dba..3a34c7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -157,6 +157,7 @@ dependencies = [ "strum_macros", "tokio", "toml", + "uuid", "webbrowser", ] @@ -889,6 +890,12 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + [[package]] name = "proc-macro-crate" version = "1.1.3" @@ -917,6 +924,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.2.13" @@ -1371,6 +1408,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "uuid" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +dependencies = [ + "getrandom", + "rand", +] + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index 761f927..a6e6247 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,5 +66,9 @@ version = "0.5" version = "1" features = ["rt-multi-thread", "net", "macros"] +[dependencies.uuid] +version = "1.1" +features = ["v4", "fast-rng"] + [dependencies.webbrowser] version = "0.7" diff --git a/src/commands/schedule_csv.rs b/src/commands/schedule_csv.rs index 51e7ab9..c37a89b 100644 --- a/src/commands/schedule_csv.rs +++ b/src/commands/schedule_csv.rs @@ -6,13 +6,15 @@ use { crate::commands::import, crate::constants, crate::recipe, - anyhow::Result, + anyhow::{bail, Result}, chrono::naive::{NaiveDate, NaiveDateTime, NaiveTime}, chrono::{DateTime, Local, TimeZone}, futures::future::try_join_all, ics::properties as calprop, ics::Event, + reqwest::StatusCode, std::collections::{HashMap, HashSet}, + std::fmt::Write, std::iter::Iterator, std::path::Path, }; @@ -53,11 +55,11 @@ pub async fn with(api_client: &ApiClient, calendar: &str, csv_file: &Path) -> Re }) .flatten(); - publish_events(&api_client, calendar, events)?; + publish_events(&api_client, calendar, events).await?; Ok(()) } -fn to_event(date: NaiveDate, meal: Meal, recipe: &recipe::Recipe) -> Event { +fn to_event(date: NaiveDate, meal: Meal, recipe: &recipe::Recipe) -> (String, Event) { // TODO: this is momentarily hardcoded, should be an option let meal_time = match meal { Meal::Lunch => NaiveTime::from_hms(12, 00, 00), @@ -74,7 +76,7 @@ fn to_event(date: NaiveDate, meal: Meal, recipe: &recipe::Recipe) -> Event { let end_time = NaiveDateTime::new(date, meal_time); let start_time = end_time - recipe.total_time(); - let mut event = Event::new(uid, dt_fmt(&Local::now())); + let mut event = Event::new(uid.clone(), dt_fmt(&Local::now())); event.push(calprop::Summary::new(&recipe.name)); event.push(calprop::Description::new(format!("cookbook@{}", recipe.id))); event.push(calprop::Location::new(&recipe.url)); @@ -91,7 +93,7 @@ fn to_event(date: NaiveDate, meal: Meal, recipe: &recipe::Recipe) -> Event { ); event.add_alarm(alarm); - event + (uid, event) } fn urls_from_csv<'a, RecordsIter>(records: RecordsIter) -> Result> @@ -112,7 +114,7 @@ where } fn dt_fmt(datetime: &DateTime) -> String { - datetime.format("%Y%m%dT%H%M%SZ").to_string() + datetime.format("%Y%m%dT%H%M%S").to_string() } async fn get_all_recipes(api_client: &ApiClient) -> Result> { @@ -148,21 +150,15 @@ async fn get_all_recipes(api_client: &ApiClient) -> Result( +async fn publish_events<'a, EventsIter>( api_client: &ApiClient, calendar: &str, events: EventsIter, ) -> Result<()> where - EventsIter: Iterator>, + EventsIter: Iterator)>, { - let calendar_url = api_client.base_url.join(&format!( - "remote.php/dav/calendars/{}/{}/", - &api_client.username, - calendar.to_lowercase().as_str().replace(" ", "-") - )); - - let cal = ics::ICalendar::new( + let calendar_prototype: ics::ICalendar = ics::ICalendar::new( "2.0", format!( "-//{}//NONSGML {}//EN", @@ -170,12 +166,48 @@ where env!("CARGO_PKG_NAME") ), ); - let cal = events.fold(cal, |mut cal, e| { - cal.add_event(e); - cal + + let dav_base = api_client + .rest + .head(api_client.base_url.join("/.well-known/caldav")?) + .send() + .await?; + + let calendar_url = dav_base.url().join(&format!( + "calendars/{}/{}/", + &api_client.username, + calendar.to_lowercase().as_str().replace(" ", "-") + ))?; + + let calendar_prototype = &calendar_prototype; + let calendar_url = &calendar_url; + let update_requests = events.map(|(event_id, event)| async move { + let mut cal = calendar_prototype.clone(); + cal.add_event(event); + + let url = calendar_url.join(&format!("{}.ics", event_id)).unwrap(); + api_client + .rest + .put(url) + .header("Content-Type", "text/calendar; charset=utf-8") + .body(cal.to_string()) + .send() + .await }); - println!("CALENDAR: \n {:?}", cal); + let responses = try_join_all(update_requests).await?; + let failed_responses = responses.into_iter().filter(|response| { + ![StatusCode::NO_CONTENT, StatusCode::CREATED].contains(&response.status()) + }); + + let mut errors = String::new(); + for r in failed_responses { + write!(errors, "\n{}", r.text().await.unwrap())?; + } + + if !errors.is_empty() { + bail!("Error while updating calendar events: {}", errors); + } Ok(()) }