From e1b6017f061498df76f517de6cd34d703da4698b Mon Sep 17 00:00:00 2001 From: Matteo Settenvini Date: Sun, 31 Jul 2022 13:41:43 +0200 Subject: [PATCH] Save back markdown file to server --- src/commands/groceries.rs | 73 +++++++++++++++++++++++++++++++++++---- src/main.rs | 6 ++-- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/commands/groceries.rs b/src/commands/groceries.rs index b887971..7ddbd56 100644 --- a/src/commands/groceries.rs +++ b/src/commands/groceries.rs @@ -1,26 +1,32 @@ +use reqwest::Method; + // SPDX-FileCopyrightText: 2022 Matteo Settenvini // 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) -> Result { "# 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::>(); + 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 + )), + } +} diff --git a/src/main.rs b/src/main.rs index deaa747..d9ec382 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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!( "")) + .arg(arg!( "The name of the calendar to read from for scheduled recipes")) + .arg(arg!( "The relative path for the Markdown file that will be saved on the server. Recommended ending it in '.md'.")) .arg(arg!(-d --days "Number of days in the future to consider") .value_parser(clap::builder::RangedU64ValueParser::::new().range(1..)) .required(false) @@ -94,7 +95,8 @@ async fn parse_args(args: &ArgMatches) -> Result<()> { .get_one::("calendar_name") .expect(" is a mandatory parameter, it cannot be missing"); let days = sub_matches.get_one::("days").unwrap(); - commands::groceries::with(&api_client, calendar_name.as_str(), *days).await + let filename = sub_matches.get_one::("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)?;