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

@ -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(" ", "-")
))?;