Partially refactor Event to change ical impl more freely
This commit is contained in:
parent
defcb94ee7
commit
32ccc1ed72
7 changed files with 616 additions and 427 deletions
|
@ -5,18 +5,17 @@ use {
|
|||
crate::api_client::ApiClient,
|
||||
crate::commands::import,
|
||||
crate::constants,
|
||||
crate::event::{Event, Meal},
|
||||
crate::recipe,
|
||||
anyhow::{bail, Result},
|
||||
chrono::naive::{NaiveDate, NaiveDateTime, NaiveTime},
|
||||
chrono::{DateTime, Local, TimeZone},
|
||||
chrono::naive::NaiveDate,
|
||||
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,
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
|
@ -26,12 +25,6 @@ struct CsvRecord {
|
|||
dinner: String,
|
||||
}
|
||||
|
||||
#[derive(strum_macros::Display)]
|
||||
enum Meal {
|
||||
Lunch,
|
||||
Dinner,
|
||||
}
|
||||
|
||||
pub async fn with(api_client: &ApiClient, calendar: &str, csv_file: &Path) -> Result<()> {
|
||||
let mut csv = csv::Reader::from_path(csv_file)?;
|
||||
let records = csv.deserialize::<CsvRecord>().flatten().collect::<Vec<_>>();
|
||||
|
@ -47,8 +40,8 @@ pub async fn with(api_client: &ApiClient, calendar: &str, csv_file: &Path) -> Re
|
|||
let dinner = recipes.get(&r.dinner);
|
||||
|
||||
let events = [
|
||||
lunch.map(|recipe| to_event(r.day, Meal::Lunch, recipe)),
|
||||
dinner.map(|recipe| to_event(r.day, Meal::Dinner, recipe)),
|
||||
lunch.map(|recipe| Event::new(r.day, Meal::Lunch, recipe.clone())),
|
||||
dinner.map(|recipe| Event::new(r.day, Meal::Dinner, recipe.clone())),
|
||||
];
|
||||
|
||||
events
|
||||
|
@ -59,46 +52,6 @@ pub async fn with(api_client: &ApiClient, calendar: &str, csv_file: &Path) -> Re
|
|||
Ok(())
|
||||
}
|
||||
|
||||
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),
|
||||
Meal::Dinner => NaiveTime::from_hms(19, 00, 00),
|
||||
};
|
||||
|
||||
let uid = format!(
|
||||
"{}-{}@{}.montecristosoftware.eu",
|
||||
date,
|
||||
meal,
|
||||
env!("CARGO_PKG_NAME")
|
||||
);
|
||||
|
||||
let end_time = NaiveDateTime::new(date, meal_time);
|
||||
let start_time = end_time - recipe.total_time();
|
||||
|
||||
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));
|
||||
event.push(calprop::DtStart::new(dt_fmt(
|
||||
&Local.from_local_datetime(&start_time).unwrap(),
|
||||
)));
|
||||
event.push(calprop::DtEnd::new(dt_fmt(
|
||||
&Local.from_local_datetime(&end_time).unwrap(),
|
||||
)));
|
||||
|
||||
// TODO make configurable yearly repetition, for now this suits my personal uses
|
||||
event.push(calprop::RRule::new("FREQ=YEARLY"));
|
||||
|
||||
let alarm = ics::Alarm::display(
|
||||
calprop::Trigger::new("-P15M"),
|
||||
calprop::Description::new(&recipe.name),
|
||||
);
|
||||
event.add_alarm(alarm);
|
||||
|
||||
(uid, event)
|
||||
}
|
||||
|
||||
fn urls_from_csv<'a, RecordsIter>(records: RecordsIter) -> Result<HashSet<String>>
|
||||
where
|
||||
RecordsIter: Iterator<Item = &'a CsvRecord>,
|
||||
|
@ -116,11 +69,7 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
fn dt_fmt(datetime: &DateTime<Local>) -> 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, Rc<recipe::Recipe>>> {
|
||||
let metadata = api_client
|
||||
.rest
|
||||
.get(api_client.base_url.join("apps/cookbook/api/recipes")?)
|
||||
|
@ -144,7 +93,7 @@ async fn get_all_recipes(api_client: &ApiClient) -> Result<HashMap<String, recip
|
|||
"Cannot fetch recipe {} with id {}",
|
||||
rm.name, rm.id
|
||||
));
|
||||
response.json::<recipe::Recipe>().await
|
||||
response.json::<recipe::Recipe>().await.map(|r| Rc::new(r))
|
||||
});
|
||||
|
||||
let recipes = try_join_all(recipes).await?;
|
||||
|
@ -159,7 +108,7 @@ async fn publish_events<'a, EventsIter>(
|
|||
events: EventsIter,
|
||||
) -> Result<()>
|
||||
where
|
||||
EventsIter: Iterator<Item = (String, Event<'a>)>,
|
||||
EventsIter: Iterator<Item = Event>,
|
||||
{
|
||||
let calendar_prototype: ics::ICalendar = ics::ICalendar::new(
|
||||
"2.0",
|
||||
|
@ -184,11 +133,11 @@ where
|
|||
|
||||
let calendar_prototype = &calendar_prototype;
|
||||
let calendar_url = &calendar_url;
|
||||
let update_requests = events.map(|(event_id, event)| async move {
|
||||
let update_requests = events.map(|ev| async move {
|
||||
let url = calendar_url.join(&format!("{}.ics", ev.uid)).unwrap();
|
||||
let mut cal = calendar_prototype.clone();
|
||||
cal.add_event(event);
|
||||
cal.add_event(ev.into());
|
||||
|
||||
let url = calendar_url.join(&format!("{}.ics", event_id)).unwrap();
|
||||
api_client
|
||||
.rest
|
||||
.put(url)
|
||||
|
|
80
src/event.rs
Normal file
80
src/event.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use {
|
||||
crate::recipe::Recipe,
|
||||
chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone},
|
||||
ics::properties as calprop,
|
||||
ics::Event as CalEvent,
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
pub struct Event {
|
||||
pub uid: String,
|
||||
pub ends_at: NaiveDateTime,
|
||||
pub recipe: Rc<Recipe>,
|
||||
}
|
||||
|
||||
#[derive(strum_macros::Display)]
|
||||
pub enum Meal {
|
||||
Lunch,
|
||||
Dinner,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
pub fn new(date: NaiveDate, meal: Meal, recipe: Rc<Recipe>) -> Self {
|
||||
let uid = format!(
|
||||
"{}-{}@{}.montecristosoftware.eu",
|
||||
date,
|
||||
meal,
|
||||
env!("CARGO_PKG_NAME")
|
||||
);
|
||||
|
||||
let meal_time = match meal {
|
||||
Meal::Lunch => NaiveTime::from_hms(12, 00, 00),
|
||||
Meal::Dinner => NaiveTime::from_hms(19, 00, 00),
|
||||
};
|
||||
|
||||
let ends_at = NaiveDateTime::new(date, meal_time);
|
||||
|
||||
Event {
|
||||
uid,
|
||||
ends_at,
|
||||
recipe,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Event> for CalEvent<'a> {
|
||||
fn from(ev: Event) -> Self {
|
||||
let start_time = ev.ends_at - ev.recipe.total_time();
|
||||
|
||||
let mut event = ics::Event::new(ev.uid.clone(), dt_fmt(&Local::now()));
|
||||
event.push(calprop::Summary::new(ev.recipe.name.clone()));
|
||||
event.push(calprop::Description::new(format!(
|
||||
"cookbook@{}",
|
||||
ev.recipe.id
|
||||
)));
|
||||
event.push(calprop::Location::new(ev.recipe.url.clone()));
|
||||
event.push(calprop::DtStart::new(dt_fmt(
|
||||
&Local.from_local_datetime(&start_time).unwrap(),
|
||||
)));
|
||||
event.push(calprop::DtEnd::new(dt_fmt(
|
||||
&Local.from_local_datetime(&ev.ends_at).unwrap(),
|
||||
)));
|
||||
|
||||
// TODO make configurable yearly repetition, for now this suits my personal uses
|
||||
event.push(calprop::RRule::new("FREQ=YEARLY"));
|
||||
|
||||
let alarm = ics::Alarm::display(
|
||||
calprop::Trigger::new("-P15M"),
|
||||
calprop::Description::new(ev.recipe.name.clone()),
|
||||
);
|
||||
event.add_alarm(alarm);
|
||||
event
|
||||
}
|
||||
}
|
||||
|
||||
fn dt_fmt(datetime: &DateTime<Local>) -> String {
|
||||
datetime.format("%Y%m%dT%H%M%S").to_string()
|
||||
}
|
|
@ -5,6 +5,7 @@ mod api_client;
|
|||
mod commands;
|
||||
mod config;
|
||||
mod constants;
|
||||
mod event;
|
||||
mod recipe;
|
||||
|
||||
use {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue