Cover use case of manually inserted recipes; allow recipe names instead of URLs in CSV

This commit is contained in:
Matteo Settenvini 2022-08-05 15:53:57 +02:00
parent fe363c0e26
commit 7495682e41
Signed by: matteo
GPG Key ID: 8576CC1AD97D42DF
7 changed files with 96 additions and 103 deletions

View File

@ -128,7 +128,7 @@
"2022-05-07","sabato",,"https://ricette.giallozafferano.it/Moussaka.html"
"2022-05-08","domenica",,
"2022-05-09","lunedì",,
"2022-05-10","martedì",,
"2022-05-10","martedì",,"https://ricette.giallozafferano.it/Torta-salata-di-melanzane.html"
"2022-05-11","mercoledì",,"https://ricette.giallozafferano.it/Mezze-maniche-al-tonno.html"
"2022-05-12","giovedì",,
"2022-05-13","venerdì",,"https://ricette.giallozafferano.it/Spiedini-di-pollo.html"
@ -193,7 +193,7 @@
"2022-07-11","lunedì",,"https://ricette.giallozafferano.it/Insalata-con-uova-strapazzate.html"
"2022-07-12","martedì",,
"2022-07-13","mercoledì",,"https://ricette.giallozafferano.it/Pasta-fredda-con-pesto-senz-aglio.html"
"2022-07-14","giovedì",,
"2022-07-14","giovedì",,"Tomatenpita"
"2022-07-15","venerdì",,"https://ricette.giallozafferano.it/Salmorejo.html"
"2022-07-16","sabato",,
"2022-07-17","domenica",,"https://ricette.giallozafferano.it/Insalata-di-quinoa-alla-greca.html"
@ -213,17 +213,17 @@
"2022-07-31","domenica",,
"2022-08-01","lunedì",,"https://ricette.giallozafferano.it/Riso-freddo-con-tonno-zucchine-e-limone.html"
"2022-08-02","martedì",,"https://ricette.giallozafferano.it/Insalata-Shirazi.html"
"2022-08-03","mercoledì",,"https://ricette.giallozafferano.it/Pasta-fredda-con-pesto-senz-aglio.html"
"2022-08-03","mercoledì",,"https://ricette.giallozafferano.it/Insalata-di-pasta-Mediterranea.html"
"2022-08-04","giovedì",,"https://ricette.giallozafferano.it/Petto-di-pollo-ai-peperoni.html"
"2022-08-05","venerdì",,"https://ricette.giallozafferano.it/Pasta-con-pomodorini-e-stracchino.html"
"2022-08-06","sabato","https://ricette.giallozafferano.it/Spaghetti-di-riso-con-carne-e-verdure.html",
"2022-08-07","domenica",,
"2022-08-08","lunedì",,
"2022-08-09","martedì",,"https://ricette.giallozafferano.it/Insalata-di-pasta-Mediterranea.html"
"2022-08-05","venerdì",,
"2022-08-06","sabato","https://ricette.giallozafferano.it/Spaghetti-di-riso-con-carne-e-verdure.html","https://ricette.giallozafferano.it/Pasta-con-pomodorini-e-stracchino.html"
"2022-08-07","domenica",,"https://ricette.giallozafferano.it/Insalata-di-riso-vegetariana.html"
"2022-08-08","lunedì",,"https://ricette.giallozafferano.it/Torta-salata-di-melanzane.html"
"2022-08-09","martedì",,"https://ricette.giallozafferano.it/Pasta-fredda-con-pesto-senz-aglio.html"
"2022-08-10","mercoledì",,"https://ricette.giallozafferano.it/Insalata-con-uova-strapazzate.html"
"2022-08-11","giovedì",,"https://ricette.giallozafferano.it/Verdure-gratinate-al-forno.html"
"2022-08-12","venerdì",,"https://ricette.giallozafferano.it/Garganelli-con-pesto-di-zucchine-e-gamberetti.html"
"2022-08-13","sabato","https://ricette.giallozafferano.it/Insalata-di-bulgur-vegana.html",
"2022-08-13","sabato","https://ricette.giallozafferano.it/Insalata-di-bulgur-vegana.html","Tomatenpita"
"2022-08-14","domenica",,
"2022-08-15","lunedì",,"https://ricette.giallozafferano.it/Pasta-con-le-melanzane.html"
"2022-08-16","martedì",,"https://ricette.giallozafferano.it/Tempeh-alle-verdure.html"
@ -254,7 +254,7 @@
"2022-09-10","sabato","https://ricette.giallozafferano.it/Maccheroncini-al-fume.html",
"2022-09-11","domenica","https://ricette.giallozafferano.it/Polpo-alla-Luciana.html",
"2022-09-12","lunedì",,"https://ricette.giallozafferano.it/Pennette-con-speck-e-zucchine.html"
"2022-09-13","martedì",,
"2022-09-13","martedì",,"https://ricette.giallozafferano.it/Torta-salata-di-melanzane.html"
"2022-09-14","mercoledì",,"https://ricette.giallozafferano.it/Scaloppine-ai-funghi.html"
"2022-09-15","giovedì",,
"2022-09-16","venerdì",,"https://ricette.giallozafferano.it/Cordon-bleu-di-melanzane.html"

1 day wday lunch dinner
128 2022-05-07 sabato https://ricette.giallozafferano.it/Moussaka.html
129 2022-05-08 domenica
130 2022-05-09 lunedì
131 2022-05-10 martedì https://ricette.giallozafferano.it/Torta-salata-di-melanzane.html
132 2022-05-11 mercoledì https://ricette.giallozafferano.it/Mezze-maniche-al-tonno.html
133 2022-05-12 giovedì
134 2022-05-13 venerdì https://ricette.giallozafferano.it/Spiedini-di-pollo.html
193 2022-07-11 lunedì https://ricette.giallozafferano.it/Insalata-con-uova-strapazzate.html
194 2022-07-12 martedì
195 2022-07-13 mercoledì https://ricette.giallozafferano.it/Pasta-fredda-con-pesto-senz-aglio.html
196 2022-07-14 giovedì Tomatenpita
197 2022-07-15 venerdì https://ricette.giallozafferano.it/Salmorejo.html
198 2022-07-16 sabato
199 2022-07-17 domenica https://ricette.giallozafferano.it/Insalata-di-quinoa-alla-greca.html
213 2022-07-31 domenica
214 2022-08-01 lunedì https://ricette.giallozafferano.it/Riso-freddo-con-tonno-zucchine-e-limone.html
215 2022-08-02 martedì https://ricette.giallozafferano.it/Insalata-Shirazi.html
216 2022-08-03 mercoledì https://ricette.giallozafferano.it/Pasta-fredda-con-pesto-senz-aglio.html https://ricette.giallozafferano.it/Insalata-di-pasta-Mediterranea.html
217 2022-08-04 giovedì https://ricette.giallozafferano.it/Petto-di-pollo-ai-peperoni.html
218 2022-08-05 venerdì https://ricette.giallozafferano.it/Pasta-con-pomodorini-e-stracchino.html
219 2022-08-06 sabato https://ricette.giallozafferano.it/Spaghetti-di-riso-con-carne-e-verdure.html https://ricette.giallozafferano.it/Pasta-con-pomodorini-e-stracchino.html
220 2022-08-07 domenica https://ricette.giallozafferano.it/Insalata-di-riso-vegetariana.html
221 2022-08-08 lunedì https://ricette.giallozafferano.it/Torta-salata-di-melanzane.html
222 2022-08-09 martedì https://ricette.giallozafferano.it/Insalata-di-pasta-Mediterranea.html https://ricette.giallozafferano.it/Pasta-fredda-con-pesto-senz-aglio.html
223 2022-08-10 mercoledì https://ricette.giallozafferano.it/Insalata-con-uova-strapazzate.html
224 2022-08-11 giovedì https://ricette.giallozafferano.it/Verdure-gratinate-al-forno.html
225 2022-08-12 venerdì https://ricette.giallozafferano.it/Garganelli-con-pesto-di-zucchine-e-gamberetti.html
226 2022-08-13 sabato https://ricette.giallozafferano.it/Insalata-di-bulgur-vegana.html Tomatenpita
227 2022-08-14 domenica
228 2022-08-15 lunedì https://ricette.giallozafferano.it/Pasta-con-le-melanzane.html
229 2022-08-16 martedì https://ricette.giallozafferano.it/Tempeh-alle-verdure.html
254 2022-09-10 sabato https://ricette.giallozafferano.it/Maccheroncini-al-fume.html
255 2022-09-11 domenica https://ricette.giallozafferano.it/Polpo-alla-Luciana.html
256 2022-09-12 lunedì https://ricette.giallozafferano.it/Pennette-con-speck-e-zucchine.html
257 2022-09-13 martedì https://ricette.giallozafferano.it/Torta-salata-di-melanzane.html
258 2022-09-14 mercoledì https://ricette.giallozafferano.it/Scaloppine-ai-funghi.html
259 2022-09-15 giovedì
260 2022-09-16 venerdì https://ricette.giallozafferano.it/Cordon-bleu-di-melanzane.html

View File

@ -9,10 +9,10 @@ use {
pub struct ApiClient {
rest: reqwest::Client,
pub(crate) rest_semaphore: Arc<Semaphore>, // TODO: wrap in dereferentiable struct
rest_semaphore: Arc<Semaphore>,
base_url: Url,
caldav_base_url: Url,
username: String,
webdav_base_url: Url,
}
impl ApiClient {
@ -30,6 +30,8 @@ impl ApiClient {
)
})?;
let username = server.login_name.clone();
use reqwest::header;
let mut default_headers = header::HeaderMap::new();
let mut auth_header = b"Basic ".to_vec();
@ -53,12 +55,14 @@ impl ApiClient {
.send(),
)?
.url()
.clone();
.join(&format!("calendars/{}/", &username))?;
let webdav_base_url = base_url.join(&format!("remote.php/dav/files/{}/", username))?;
Ok(ApiClient {
base_url,
caldav_base_url,
username: server.login_name.clone(),
webdav_base_url,
rest: rest_client,
rest_semaphore: Arc::new(Semaphore::new(5)),
})
@ -94,10 +98,6 @@ impl ApiClient {
&self.caldav_base_url
}
pub fn username(&self) -> &str {
&self.username
}
pub async fn get_events<Tz>(
&self,
calendar_name: &str,
@ -113,10 +113,8 @@ impl ApiClient {
let response = client
.request(
report_method.clone(),
// TODO extract into helper method
self.caldav_base_url.join(&format!(
"calendars/{}/{}",
self.username(),
"{}/",
calendar_name.to_lowercase().replace(" ", "-")
))?,
)
@ -195,4 +193,8 @@ impl ApiClient {
Ok(events.collect::<Vec<_>>())
}
pub fn webdav_base_url(&self) -> &Url {
&self.webdav_base_url
}
}

View File

@ -8,7 +8,7 @@ use {
chrono::{Duration, Local},
icalendar::Component,
regex::Regex,
reqwest::{Method, StatusCode},
reqwest::{Method, StatusCode, Url},
std::collections::HashSet,
std::ops::Range,
};
@ -109,43 +109,16 @@ fn prepare_grocery_list(ingredients: &Vec<Ingredient>) -> Result<String> {
}
async fn save_grocery_list(api_client: &ApiClient, filename: &str, contents: &str) -> Result<()> {
let dav_base_url = api_client
.base_url()
.join(&format!("remote.php/dav/files/{}/", api_client.username()))?;
let filename_components = filename.split('/').collect::<Vec<_>>();
filename_components
.iter()
.take(filename_components.len() - 1)
.fold(Ok(dav_base_url.clone()), |url, dir| {
.fold(Ok(api_client.webdav_base_url().clone()), |url, dir| {
url.map(|u| u.join(&format!("{dir}/")).unwrap())
.and_then(|url| {
futures::executor::block_on(async {
let response = api_client
.rest(|client| async {
let r = client
.request(Method::from_bytes(b"MKCOL").unwrap(), url.clone())
.send()
.await;
Ok(r?)
})
.await;
match response.map(|r| r.status()) {
Ok(StatusCode::OK)
| Ok(StatusCode::METHOD_NOT_ALLOWED /* already exists */) => Ok(url),
Ok(status) => Err(anyhow!(
"Could not create WebDAV collection {}, server responded with {}",
&url,
status
)),
Err(e) => Err(anyhow!(e)),
}
})
})
.and_then(|url| ensure_collection_exist(api_client, url))
})?;
let file_url = dav_base_url.join(filename).unwrap();
let file_url = api_client.webdav_base_url().join(filename).unwrap();
log::info!("Saving grocery list to {}", &file_url);
let response = api_client
.rest(|client| async {
@ -168,3 +141,27 @@ async fn save_grocery_list(api_client: &ApiClient, filename: &str, contents: &st
)),
}
}
fn ensure_collection_exist(api_client: &ApiClient, url: Url) -> Result<Url> {
futures::executor::block_on(async {
let response = api_client
.rest(|client| async {
let r = client
.request(Method::from_bytes(b"MKCOL").unwrap(), url.clone())
.send()
.await;
Ok(r?)
})
.await;
match response.map(|r| r.status()) {
Ok(StatusCode::OK) | Ok(StatusCode::METHOD_NOT_ALLOWED /* already exists */) => Ok(url),
Ok(status) => Err(anyhow!(
"Could not create WebDAV collection {}, server responded with {}",
&url,
status
)),
Err(e) => Err(anyhow!(e)),
}
})
}

View File

@ -10,10 +10,8 @@ pub async fn with(api_client: &ApiClient, calendar_name: &str) -> Result<()> {
let response = client
.request(
report_method.clone(),
// TODO extract into helper method
api_client.caldav_base_url().join(&format!(
"calendars/{}/{}/",
api_client.username(),
"{}/",
calendar_name.to_lowercase().replace(" ", "-")
))?,
)

View File

@ -11,7 +11,7 @@ use {
chrono::naive::NaiveDate,
futures::future::try_join_all,
icalendar::Event,
reqwest::StatusCode,
reqwest::{StatusCode, Url},
std::collections::{HashMap, HashSet},
std::fmt::Write,
std::iter::Iterator,
@ -61,17 +61,17 @@ pub async fn with(
Ok(())
}
fn urls_from_csv<'a, RecordsIter>(records: RecordsIter) -> Result<HashSet<String>>
fn urls_from_csv<'a, RecordsIter>(records: RecordsIter) -> Result<HashSet<Url>>
where
RecordsIter: Iterator<Item = &'a CsvRecord>,
{
Ok(
records.fold(std::collections::HashSet::new(), |mut set, r| {
if !r.lunch.is_empty() {
set.insert(r.lunch.clone());
if let Ok(lunch) = Url::try_from(r.lunch.as_str()) {
set.insert(lunch);
}
if !r.dinner.is_empty() {
set.insert(r.dinner.clone());
if let Ok(dinner) = Url::try_from(r.dinner.as_str()) {
set.insert(dinner);
}
set
}),
@ -93,35 +93,20 @@ async fn get_all_recipes(api_client: &ApiClient) -> Result<HashMap<String, Rc<re
.await?;
let recipes = metadata.iter().map(|rm| async {
// TODO: wrap this, heavily refactor this mess
let _ = api_client.rest_semaphore.acquire().await.unwrap();
let mut retries = 0;
let response = loop {
let result = api_client
.rest(|client| async {
let response = client
.get(
api_client
.base_url()
.join(&format!("apps/cookbook/api/recipes/{id}", id = rm.id))
.unwrap(),
)
.send()
.await;
Ok(response?)
})
.await;
match result {
Ok(response) => break response,
Err(_) if retries < 10 => {
retries += 1;
std::thread::sleep(retries * std::time::Duration::from_millis(500));
continue;
}
_ => bail!("Cannot fetch recipe {} with id {}", rm.name, rm.id),
}
};
let response = api_client
.rest(|client| async {
let r = client
.get(
api_client
.base_url()
.join(&format!("apps/cookbook/api/recipes/{id}", id = rm.id))
.unwrap(),
)
.send()
.await;
Ok(r?)
})
.await?;
response
.json::<recipe::Recipe>()
@ -131,9 +116,17 @@ async fn get_all_recipes(api_client: &ApiClient) -> Result<HashMap<String, Rc<re
});
let recipes = try_join_all(recipes).await?;
Ok(HashMap::from_iter(
recipes.into_iter().map(|r| (r.url.clone(), r)),
))
Ok(HashMap::from_iter(recipes.into_iter().map(|r| {
// Fallback to recipe name as ID if URL is empty
let id = match &r.url {
Some(u) if !u.is_empty() => u,
_ => &r.name,
}
.clone();
(id, r)
})))
}
async fn publish_events<'a, SchedulingsIter>(
@ -146,8 +139,7 @@ where
SchedulingsIter: Iterator<Item = Scheduling>,
{
let calendar_url = api_client.caldav_base_url().join(&format!(
"calendars/{}/{}/",
&api_client.username(),
"{}/",
calendar.to_lowercase().as_str().replace(" ", "-")
))?;

View File

@ -12,7 +12,7 @@ pub struct Metadata {
#[serde(rename = "recipe_id")]
pub id: u32,
pub name: String,
pub keywords: String,
pub keywords: Option<String>,
pub date_created: DateTime,
pub date_modified: DateTime,
}
@ -24,8 +24,8 @@ pub struct Recipe {
pub id: isize,
pub name: String,
pub description: String,
pub url: String,
pub keywords: String,
pub url: Option<String>,
pub keywords: Option<String>,
#[serde(rename = "dateCreated")]
pub created: DateTime,

View File

@ -49,16 +49,20 @@ impl From<Scheduling> for Event {
use icalendar::Component;
let start_time = ev.ends_at - ev.recipe.total_time();
let cal_event = Event::new()
let mut cal_event = Event::new();
cal_event
.uid(&ev.uid)
.summary(&ev.recipe.name)
.description(&format!("cookbook@{}", ev.recipe.id))
.location(&ev.recipe.url)
.timestamp(Utc::now())
.starts(start_time)
.ends(ev.ends_at)
.done();
.ends(ev.ends_at);
cal_event
if let Some(ref location) = ev.recipe.url.clone() {
cal_event.location(&location);
}
cal_event.done()
}
}