Save back markdown file to server

This commit is contained in:
Matteo Settenvini 2022-07-31 13:41:43 +02:00
parent 1510eb4f3d
commit e1b6017f06
Signed by: matteo
GPG Key ID: 8576CC1AD97D42DF
2 changed files with 70 additions and 9 deletions

View File

@ -1,26 +1,32 @@
use reqwest::Method;
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
// SPDX-License-Identifier: AGPL-3.0-or-later
use {
crate::api_client::ApiClient,
crate::recipe::{Ingredient, Recipe},
anyhow::Result,
anyhow::{anyhow, Result},
chrono::{Duration, Local},
icalendar::Component,
regex::Regex,
reqwest::StatusCode,
std::collections::HashSet,
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 ingredients = get_ingredients(api_client, ids).await?;
let ingredients = merge_ingredients(ingredients);
let md = prepare_grocery_list(&ingredients)?;
println!("{}", md);
// save_grocery_list(api_client, "", md).await?;
// TODO filename is hardcoded for now
save_grocery_list(api_client, location, &md).await?;
Ok(())
}
@ -89,10 +95,63 @@ fn prepare_grocery_list(ingredients: &Vec<Ingredient>) -> Result<String> {
"# Grocery list - {}",
chrono::Local::now().format("%Y-%m-%d").to_string()
)?;
writeln!(out)?; // leave an empty line
for ingredient in ingredients {
let ingredient = ingredient.0.as_str();
writeln!(out, "* {}", ingredient)?;
writeln!(out, "- [ ] {}", ingredient)?;
}
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
)),
}
}

View File

@ -45,7 +45,8 @@ fn setup_args() -> ArgMatches {
Command::new("groceries")
.about("Create a grocery list from scheduled calendar events")
.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")
.value_parser(clap::builder::RangedU64ValueParser::<u32>::new().range(1..))
.required(false)
@ -94,7 +95,8 @@ async fn parse_args(args: &ArgMatches) -> Result<()> {
.get_one::<String>("calendar_name")
.expect("<calendar_name> is a mandatory parameter, it cannot be missing");
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)) => {
let api_client = get_api_client(&sub_matches, &configuration)?;