Add purge command, simplify event RRULE until clients are fixed
This commit is contained in:
parent
04fadd3d89
commit
76968d5977
|
@ -19,7 +19,10 @@
|
|||
}
|
||||
},
|
||||
"args": ["groceries", "Cucina"],
|
||||
"cwd": "${workspaceFolder}"
|
||||
"env": {
|
||||
"RUST_LOG": "debug",
|
||||
},
|
||||
"cwd": "${workspaceFolder}",
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
|
@ -37,6 +40,30 @@
|
|||
}
|
||||
},
|
||||
"args": ["schedule-csv", "Cucina", "examples/example-schedule.csv"],
|
||||
"env": {
|
||||
"RUST_LOG": "debug",
|
||||
},
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug executable 'cook' -> purge",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--bin=cook",
|
||||
"--package=cooking-schedule"
|
||||
],
|
||||
"filter": {
|
||||
"name": "cook",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": ["purge", "Cucina"],
|
||||
"env": {
|
||||
"RUST_LOG": "debug",
|
||||
},
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
|
@ -56,6 +83,9 @@
|
|||
}
|
||||
},
|
||||
"args": [],
|
||||
"env": {
|
||||
"RUST_LOG": "debug",
|
||||
},
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -72,6 +72,10 @@ impl ApiClient {
|
|||
&self.base_url
|
||||
}
|
||||
|
||||
pub fn caldav_base_url(&self) -> &Url {
|
||||
&self.caldav_base_url
|
||||
}
|
||||
|
||||
pub fn username(&self) -> &str {
|
||||
&self.username
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use reqwest::Method;
|
||||
|
||||
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
|
@ -10,7 +8,7 @@ use {
|
|||
chrono::{Duration, Local},
|
||||
icalendar::Component,
|
||||
regex::Regex,
|
||||
reqwest::StatusCode,
|
||||
reqwest::{Method, StatusCode},
|
||||
std::collections::HashSet,
|
||||
std::ops::Range,
|
||||
};
|
||||
|
|
|
@ -4,5 +4,6 @@
|
|||
pub mod groceries;
|
||||
pub mod import;
|
||||
pub mod init;
|
||||
pub mod purge;
|
||||
pub mod schedule;
|
||||
pub mod schedule_csv;
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use {crate::api_client::ApiClient, crate::constants, anyhow::Result, reqwest::Method};
|
||||
|
||||
pub async fn with(api_client: &ApiClient, calendar_name: &str) -> Result<()> {
|
||||
let report_method = Method::from_bytes(b"REPORT")?;
|
||||
let events_xml = api_client
|
||||
.rest()
|
||||
.request(
|
||||
report_method,
|
||||
// TODO extract into helper method
|
||||
api_client.caldav_base_url().join(&format!(
|
||||
"calendars/{}/{}/",
|
||||
api_client.username(),
|
||||
calendar_name.to_lowercase().replace(" ", "-")
|
||||
))?,
|
||||
)
|
||||
.header("Prefer", "return-minimal")
|
||||
.header("Content-Type", "application/xml; charset=utf-8")
|
||||
.header("Depth", 1)
|
||||
.body(format!(
|
||||
"<c:calendar-query xmlns:d=\"DAV:\" xmlns:c=\"urn:ietf:params:xml:ns:caldav\">
|
||||
<d:prop>
|
||||
<d:getetag/>
|
||||
</d:prop>
|
||||
<c:filter>
|
||||
<c:comp-filter name=\"VCALENDAR\">
|
||||
<c:prop-filter name=\"PRODID\">
|
||||
<c:text-match>{}</c:text-match>
|
||||
</c:prop-filter>
|
||||
</c:comp-filter>
|
||||
</c:filter>
|
||||
</c:calendar-query>
|
||||
",
|
||||
constants::CALENDAR_PROVIDER,
|
||||
))
|
||||
.send()
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
|
||||
let xml_doc = libxml::parser::Parser::default().parse_string(events_xml.as_bytes())?;
|
||||
let mut xpath_ctx = libxml::xpath::Context::new(&xml_doc).unwrap();
|
||||
xpath_ctx.register_namespace("d", "DAV:").unwrap();
|
||||
let events_to_purge = xpath_ctx
|
||||
.findnodes("//d:response/d:href/text()", None)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|n| icalendar::parser::unfold(&n.get_content()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for url in events_to_purge {
|
||||
api_client
|
||||
.rest()
|
||||
.delete(api_client.base_url().join(&url)?)
|
||||
.send()
|
||||
.await?;
|
||||
log::debug!("Purged {}", &url);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -115,13 +115,7 @@ async fn publish_events<'a, EventsIter>(
|
|||
where
|
||||
EventsIter: Iterator<Item = Event>,
|
||||
{
|
||||
let dav_base = api_client
|
||||
.rest()
|
||||
.head(api_client.base_url().join("/.well-known/caldav")?)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let calendar_url = dav_base.url().join(&format!(
|
||||
let calendar_url = api_client.caldav_base_url().join(&format!(
|
||||
"calendars/{}/{}/",
|
||||
&api_client.username(),
|
||||
calendar.to_lowercase().as_str().replace(" ", "-")
|
||||
|
@ -160,7 +154,6 @@ where
|
|||
|
||||
// TODO: wrap this
|
||||
let _ = api_client.rest_semaphore.acquire().await.unwrap();
|
||||
log::info!("{}", info_message);
|
||||
let response = api_client
|
||||
.rest()
|
||||
.put(url)
|
||||
|
@ -169,6 +162,8 @@ where
|
|||
.send()
|
||||
.await;
|
||||
|
||||
log::info!("{}", info_message);
|
||||
|
||||
// TODO: magic numbers are bad...
|
||||
std::thread::sleep(std::time::Duration::from_millis(300));
|
||||
response
|
||||
|
|
|
@ -5,8 +5,6 @@ pub const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PK
|
|||
pub const CALENDAR_PROVIDER: &str = concat!(
|
||||
"-//IDN montecristosoftware.eu//",
|
||||
env!("CARGO_PKG_NAME"),
|
||||
" ",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
"//EN"
|
||||
);
|
||||
pub const ICAL_UTCTIME_FMT: &str = "%Y%m%dT%H%M%SZ";
|
||||
|
|
10
src/event.rs
10
src/event.rs
|
@ -3,7 +3,7 @@
|
|||
|
||||
use {
|
||||
crate::recipe::Recipe,
|
||||
chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime, Utc},
|
||||
chrono::{/*Datelike, */ NaiveDate, NaiveDateTime, NaiveTime, Utc},
|
||||
icalendar::Event as CalEvent,
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
@ -48,7 +48,7 @@ impl From<Event> for CalEvent {
|
|||
fn from(ev: Event) -> Self {
|
||||
use icalendar::Component;
|
||||
|
||||
const DAY_NAMES: [&str; 7] = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"];
|
||||
// const DAY_NAMES: [&str; 7] = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"];
|
||||
let start_time = ev.ends_at - ev.recipe.total_time();
|
||||
|
||||
let cal_event = CalEvent::new()
|
||||
|
@ -59,6 +59,7 @@ impl From<Event> for CalEvent {
|
|||
.timestamp(Utc::now())
|
||||
.starts(start_time)
|
||||
.ends(ev.ends_at)
|
||||
/* FIXME
|
||||
.add_property(
|
||||
// TODO make configurable yearly repetition, for now this suits my personal uses
|
||||
"RRULE",
|
||||
|
@ -69,6 +70,11 @@ impl From<Event> for CalEvent {
|
|||
.unwrap(),
|
||||
weekno = start_time.iso_week().week(),
|
||||
),
|
||||
)*/
|
||||
.add_property(
|
||||
// TODO make configurable yearly repetition, for now this suits my personal uses
|
||||
"RRULE",
|
||||
"FREQ=YEARLY",
|
||||
)
|
||||
.done();
|
||||
|
||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -70,6 +70,12 @@ fn setup_args() -> ArgMatches {
|
|||
.arg(arg!(<calendar_name> ""))
|
||||
.arg(arg!(<csv_file> "").value_parser(clap::value_parser!(PathBuf)))
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("purge")
|
||||
.about("Removes all events created by this application from a given calendar")
|
||||
.arg(server_arg.clone())
|
||||
.arg(arg!(<calendar_name> ""))
|
||||
)
|
||||
.get_matches()
|
||||
}
|
||||
|
||||
|
@ -114,6 +120,13 @@ async fn parse_args(args: &ArgMatches) -> Result<()> {
|
|||
.expect("<calendar_name> is a mandatory parameter, it cannot be missing");
|
||||
commands::schedule_csv::with(&api_client, calendar_name.as_str(), &csv_file).await
|
||||
}
|
||||
Some(("purge", sub_matches)) => {
|
||||
let api_client = get_api_client(&sub_matches, &configuration)?;
|
||||
let calendar_name = sub_matches
|
||||
.get_one::<String>("calendar_name")
|
||||
.expect("<calendar_name> is a mandatory parameter, it cannot be missing");
|
||||
commands::purge::with(&api_client, calendar_name.as_str()).await
|
||||
}
|
||||
_ => unreachable!("Exhausted list of subcommands and subcommand_required prevents `None`"),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue