Save back markdown file to server
This commit is contained in:
parent
1510eb4f3d
commit
e1b6017f06
|
@ -1,26 +1,32 @@
|
||||||
|
use reqwest::Method;
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::api_client::ApiClient,
|
crate::api_client::ApiClient,
|
||||||
crate::recipe::{Ingredient, Recipe},
|
crate::recipe::{Ingredient, Recipe},
|
||||||
anyhow::Result,
|
anyhow::{anyhow, Result},
|
||||||
chrono::{Duration, Local},
|
chrono::{Duration, Local},
|
||||||
icalendar::Component,
|
icalendar::Component,
|
||||||
regex::Regex,
|
regex::Regex,
|
||||||
|
reqwest::StatusCode,
|
||||||
std::collections::HashSet,
|
std::collections::HashSet,
|
||||||
std::ops::Range,
|
std::ops::Range,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn with(api_client: &ApiClient, calendar_name: &str, days: u32) -> Result<()> {
|
pub async fn with(
|
||||||
|
api_client: &ApiClient,
|
||||||
|
calendar_name: &str,
|
||||||
|
location: &str,
|
||||||
|
days: u32,
|
||||||
|
) -> Result<()> {
|
||||||
let ids = map_events_to_recipe_ids(api_client, calendar_name, days).await?;
|
let ids = map_events_to_recipe_ids(api_client, calendar_name, days).await?;
|
||||||
let ingredients = get_ingredients(api_client, ids).await?;
|
let ingredients = get_ingredients(api_client, ids).await?;
|
||||||
let ingredients = merge_ingredients(ingredients);
|
let ingredients = merge_ingredients(ingredients);
|
||||||
let md = prepare_grocery_list(&ingredients)?;
|
let md = prepare_grocery_list(&ingredients)?;
|
||||||
|
// TODO filename is hardcoded for now
|
||||||
println!("{}", md);
|
save_grocery_list(api_client, location, &md).await?;
|
||||||
|
|
||||||
// save_grocery_list(api_client, "", md).await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,10 +95,63 @@ fn prepare_grocery_list(ingredients: &Vec<Ingredient>) -> Result<String> {
|
||||||
"# Grocery list - {}",
|
"# Grocery list - {}",
|
||||||
chrono::Local::now().format("%Y-%m-%d").to_string()
|
chrono::Local::now().format("%Y-%m-%d").to_string()
|
||||||
)?;
|
)?;
|
||||||
|
writeln!(out)?; // leave an empty line
|
||||||
for ingredient in ingredients {
|
for ingredient in ingredients {
|
||||||
let ingredient = ingredient.0.as_str();
|
let ingredient = ingredient.0.as_str();
|
||||||
writeln!(out, "* {}", ingredient)?;
|
writeln!(out, "- [ ] {}", ingredient)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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| {
|
||||||
|
url.map(|u| u.join(&format!("{dir}/")).unwrap())
|
||||||
|
.and_then(|url| {
|
||||||
|
futures::executor::block_on(async {
|
||||||
|
let response = api_client
|
||||||
|
.rest()
|
||||||
|
.request(Method::from_bytes(b"MKCOL").unwrap(), url.clone())
|
||||||
|
.send()
|
||||||
|
.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)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let file_url = dav_base_url.join(filename).unwrap();
|
||||||
|
let response = api_client
|
||||||
|
.rest()
|
||||||
|
.put(file_url.clone())
|
||||||
|
.header("Content-Type", "text/markdown; charset=utf-8")
|
||||||
|
.body(contents.to_owned())
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
match response.status() {
|
||||||
|
StatusCode::CREATED | StatusCode::NO_CONTENT => Ok(()),
|
||||||
|
status => Err(anyhow!(
|
||||||
|
"Cannot save grocery list at {}, server responded with status {}",
|
||||||
|
file_url,
|
||||||
|
status
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -45,7 +45,8 @@ fn setup_args() -> ArgMatches {
|
||||||
Command::new("groceries")
|
Command::new("groceries")
|
||||||
.about("Create a grocery list from scheduled calendar events")
|
.about("Create a grocery list from scheduled calendar events")
|
||||||
.arg(server_arg.clone())
|
.arg(server_arg.clone())
|
||||||
.arg(arg!(<calendar_name> ""))
|
.arg(arg!(<calendar_name> "The name of the calendar to read from for scheduled recipes"))
|
||||||
|
.arg(arg!(<filename> "The relative path for the Markdown file that will be saved on the server. Recommended ending it in '.md'."))
|
||||||
.arg(arg!(-d --days <days> "Number of days in the future to consider")
|
.arg(arg!(-d --days <days> "Number of days in the future to consider")
|
||||||
.value_parser(clap::builder::RangedU64ValueParser::<u32>::new().range(1..))
|
.value_parser(clap::builder::RangedU64ValueParser::<u32>::new().range(1..))
|
||||||
.required(false)
|
.required(false)
|
||||||
|
@ -94,7 +95,8 @@ async fn parse_args(args: &ArgMatches) -> Result<()> {
|
||||||
.get_one::<String>("calendar_name")
|
.get_one::<String>("calendar_name")
|
||||||
.expect("<calendar_name> is a mandatory parameter, it cannot be missing");
|
.expect("<calendar_name> is a mandatory parameter, it cannot be missing");
|
||||||
let days = sub_matches.get_one::<u32>("days").unwrap();
|
let days = sub_matches.get_one::<u32>("days").unwrap();
|
||||||
commands::groceries::with(&api_client, calendar_name.as_str(), *days).await
|
let filename = sub_matches.get_one::<String>("filename").unwrap();
|
||||||
|
commands::groceries::with(&api_client, calendar_name.as_str(), filename, *days).await
|
||||||
}
|
}
|
||||||
Some(("schedule", sub_matches)) => {
|
Some(("schedule", sub_matches)) => {
|
||||||
let api_client = get_api_client(&sub_matches, &configuration)?;
|
let api_client = get_api_client(&sub_matches, &configuration)?;
|
||||||
|
|
Loading…
Reference in New Issue