Publish cal events to server

This commit is contained in:
Matteo Settenvini 2022-07-28 18:32:38 +02:00
parent dce761b0f9
commit 8e1547750e
Signed by: matteo
GPG Key ID: 8576CC1AD97D42DF
3 changed files with 102 additions and 19 deletions

47
Cargo.lock generated
View File

@ -157,6 +157,7 @@ dependencies = [
"strum_macros", "strum_macros",
"tokio", "tokio",
"toml", "toml",
"uuid",
"webbrowser", "webbrowser",
] ]
@ -889,6 +890,12 @@ version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
[[package]]
name = "ppv-lite86"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "1.1.3" version = "1.1.3"
@ -917,6 +924,36 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.13" version = "0.2.13"
@ -1371,6 +1408,16 @@ dependencies = [
"percent-encoding", "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]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"

View File

@ -66,5 +66,9 @@ version = "0.5"
version = "1" version = "1"
features = ["rt-multi-thread", "net", "macros"] features = ["rt-multi-thread", "net", "macros"]
[dependencies.uuid]
version = "1.1"
features = ["v4", "fast-rng"]
[dependencies.webbrowser] [dependencies.webbrowser]
version = "0.7" version = "0.7"

View File

@ -6,13 +6,15 @@ use {
crate::commands::import, crate::commands::import,
crate::constants, crate::constants,
crate::recipe, crate::recipe,
anyhow::Result, anyhow::{bail, Result},
chrono::naive::{NaiveDate, NaiveDateTime, NaiveTime}, chrono::naive::{NaiveDate, NaiveDateTime, NaiveTime},
chrono::{DateTime, Local, TimeZone}, chrono::{DateTime, Local, TimeZone},
futures::future::try_join_all, futures::future::try_join_all,
ics::properties as calprop, ics::properties as calprop,
ics::Event, ics::Event,
reqwest::StatusCode,
std::collections::{HashMap, HashSet}, std::collections::{HashMap, HashSet},
std::fmt::Write,
std::iter::Iterator, std::iter::Iterator,
std::path::Path, std::path::Path,
}; };
@ -53,11 +55,11 @@ pub async fn with(api_client: &ApiClient, calendar: &str, csv_file: &Path) -> Re
}) })
.flatten(); .flatten();
publish_events(&api_client, calendar, events)?; publish_events(&api_client, calendar, events).await?;
Ok(()) 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 // TODO: this is momentarily hardcoded, should be an option
let meal_time = match meal { let meal_time = match meal {
Meal::Lunch => NaiveTime::from_hms(12, 00, 00), 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 end_time = NaiveDateTime::new(date, meal_time);
let start_time = end_time - recipe.total_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::Summary::new(&recipe.name));
event.push(calprop::Description::new(format!("cookbook@{}", recipe.id))); event.push(calprop::Description::new(format!("cookbook@{}", recipe.id)));
event.push(calprop::Location::new(&recipe.url)); 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.add_alarm(alarm);
event (uid, event)
} }
fn urls_from_csv<'a, RecordsIter>(records: RecordsIter) -> Result<HashSet<String>> fn urls_from_csv<'a, RecordsIter>(records: RecordsIter) -> Result<HashSet<String>>
@ -112,7 +114,7 @@ where
} }
fn dt_fmt(datetime: &DateTime<Local>) -> String { fn dt_fmt(datetime: &DateTime<Local>) -> 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<HashMap<String, recipe::Recipe>> { async fn get_all_recipes(api_client: &ApiClient) -> Result<HashMap<String, recipe::Recipe>> {
@ -148,21 +150,15 @@ async fn get_all_recipes(api_client: &ApiClient) -> Result<HashMap<String, recip
)) ))
} }
fn publish_events<'a, EventsIter>( async fn publish_events<'a, EventsIter>(
api_client: &ApiClient, api_client: &ApiClient,
calendar: &str, calendar: &str,
events: EventsIter, events: EventsIter,
) -> Result<()> ) -> Result<()>
where where
EventsIter: Iterator<Item = Event<'a>>, EventsIter: Iterator<Item = (String, Event<'a>)>,
{ {
let calendar_url = api_client.base_url.join(&format!( let calendar_prototype: ics::ICalendar = ics::ICalendar::new(
"remote.php/dav/calendars/{}/{}/",
&api_client.username,
calendar.to_lowercase().as_str().replace(" ", "-")
));
let cal = ics::ICalendar::new(
"2.0", "2.0",
format!( format!(
"-//{}//NONSGML {}//EN", "-//{}//NONSGML {}//EN",
@ -170,12 +166,48 @@ where
env!("CARGO_PKG_NAME") env!("CARGO_PKG_NAME")
), ),
); );
let cal = events.fold(cal, |mut cal, e| {
cal.add_event(e); let dav_base = api_client
cal .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(()) Ok(())
} }