Fix outputting tzid and alarm trigger

This commit is contained in:
Matteo Settenvini 2022-07-29 19:52:50 +02:00
parent 096db12c74
commit 77404d1642
Signed by: matteo
GPG Key ID: 8576CC1AD97D42DF
7 changed files with 117 additions and 78 deletions

71
Cargo.lock generated
View File

@ -8,6 +8,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "android_system_properties"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.58" version = "1.0.58"
@ -160,6 +169,7 @@ dependencies = [
"csv", "csv",
"directories", "directories",
"futures", "futures",
"iana-time-zone",
"ics", "ics",
"minicaldav", "minicaldav",
"reqwest", "reqwest",
@ -170,7 +180,7 @@ dependencies = [
"strum_macros", "strum_macros",
"tokio", "tokio",
"toml", "toml",
"uuid", "ureq",
"webbrowser", "webbrowser",
] ]
@ -571,6 +581,19 @@ dependencies = [
"tokio-native-tls", "tokio-native-tls",
] ]
[[package]]
name = "iana-time-zone"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00c0d80ad9ca8d30ca648bf6cb1e3e3326d75071b76dbe143dd4a9cedcd58975"
dependencies = [
"android_system_properties",
"core-foundation",
"js-sys",
"wasm-bindgen",
"winapi",
]
[[package]] [[package]]
name = "ics" name = "ics"
version = "0.5.7" version = "0.5.7"
@ -944,12 +967,6 @@ version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
[[package]]
name = "ppv-lite86"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "1.1.3" version = "1.1.3"
@ -978,36 +995,6 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.13" version = "0.2.13"
@ -1529,16 +1516,6 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "uuid"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f"
dependencies = [
"getrandom",
"rand",
]
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"

View File

@ -26,6 +26,9 @@ version = "0.13"
version = "0.4" version = "0.4"
features = ["serde"] features = ["serde"]
[dependencies.iana-time-zone]
version = "0.1"
[dependencies.csv] [dependencies.csv]
version = "1.1" version = "1.1"
@ -69,9 +72,8 @@ version = "0.5"
version = "1" version = "1"
features = ["rt-multi-thread", "net", "macros"] features = ["rt-multi-thread", "net", "macros"]
[dependencies.uuid] [dependencies.ureq]
version = "1.1" version = "2.5"
features = ["v4", "fast-rng"]
[dependencies.webbrowser] [dependencies.webbrowser]
version = "0.7" version = "0.7"

View File

@ -2,18 +2,21 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
use { use {
crate::config::Config, crate::constants, anyhow::anyhow, crate::config::Config, crate::constants, anyhow::anyhow, anyhow::Result,
base64::write::EncoderWriter as Base64Encoder, reqwest::Url, std::io::Write, base64::write::EncoderWriter as Base64Encoder, reqwest::Url, std::io::Write,
}; };
pub struct ApiClient { pub struct ApiClient {
pub base_url: Url, rest: reqwest::Client,
pub username: String, agent: ureq::Agent,
pub rest: reqwest::Client, base_url: Url,
caldav_base_url: Url,
username: String,
password: String,
} }
impl ApiClient { impl ApiClient {
pub fn new(server_name: &str, configuration: &Config) -> anyhow::Result<Self> { pub fn new(server_name: &str, configuration: &Config) -> Result<Self> {
let server = configuration let server = configuration
.credentials .credentials
.servers .servers
@ -43,10 +46,58 @@ impl ApiClient {
.default_headers(default_headers) .default_headers(default_headers)
.build()?; .build()?;
let base_url = Url::parse(&server.url)?;
let caldav_base_url = futures::executor::block_on(
rest_client
.head(base_url.join("/.well-known/caldav")?)
.send(),
)?
.url()
.clone();
Ok(ApiClient { Ok(ApiClient {
base_url: Url::parse(&server.url)?, base_url,
caldav_base_url,
username: server.login_name.clone(), username: server.login_name.clone(),
password: server.password.clone(),
rest: rest_client, rest: rest_client,
agent: ureq::Agent::new(),
}) })
} }
pub fn rest(&self) -> &reqwest::Client {
&self.rest
}
pub fn base_url(&self) -> &Url {
&self.base_url
}
pub fn username(&self) -> &str {
&self.username
}
pub fn get_calendars(
&self,
) -> core::result::Result<Vec<minicaldav::Calendar>, minicaldav::Error> {
minicaldav::get_calendars(
self.agent.clone(),
self.username(),
&self.password,
&self.caldav_base_url,
)
}
pub fn get_events(
&self,
calendar: &minicaldav::Calendar,
) -> core::result::Result<(Vec<minicaldav::Event>, Vec<minicaldav::Error>), minicaldav::Error>
{
minicaldav::get_events(
self.agent.clone(),
self.username(),
&self.password,
calendar,
)
}
} }

View File

@ -10,8 +10,8 @@ where
{ {
for url in urls { for url in urls {
let response = api_client let response = api_client
.rest .rest()
.post(api_client.base_url.join("apps/cookbook/import")?) .post(api_client.base_url().join("apps/cookbook/import")?)
.json(&serde_json::json!({ .json(&serde_json::json!({
"url": url.as_ref(), "url": url.as_ref(),
})) }))

View File

@ -5,8 +5,8 @@ use {crate::api_client::ApiClient, crate::recipe, anyhow::Result};
pub async fn with(api_client: &ApiClient) -> Result<()> { pub async fn with(api_client: &ApiClient) -> Result<()> {
let recipes = api_client let recipes = api_client
.rest .rest()
.get(api_client.base_url.join("apps/cookbook/api/recipes")?) .get(api_client.base_url().join("apps/cookbook/api/recipes")?)
.send() .send()
.await?; .await?;
println!("{:#?}", recipes.json::<Vec<recipe::Metadata>>().await?); println!("{:#?}", recipes.json::<Vec<recipe::Metadata>>().await?);

View File

@ -71,8 +71,8 @@ where
async fn get_all_recipes(api_client: &ApiClient) -> Result<HashMap<String, Rc<recipe::Recipe>>> { async fn get_all_recipes(api_client: &ApiClient) -> Result<HashMap<String, Rc<recipe::Recipe>>> {
let metadata = api_client let metadata = api_client
.rest .rest()
.get(api_client.base_url.join("apps/cookbook/api/recipes")?) .get(api_client.base_url().join("apps/cookbook/api/recipes")?)
.send() .send()
.await? .await?
.json::<Vec<recipe::Metadata>>() .json::<Vec<recipe::Metadata>>()
@ -80,10 +80,10 @@ async fn get_all_recipes(api_client: &ApiClient) -> Result<HashMap<String, Rc<re
let recipes = metadata.iter().map(|rm| async { let recipes = metadata.iter().map(|rm| async {
let response = api_client let response = api_client
.rest .rest()
.get( .get(
api_client api_client
.base_url .base_url()
.join(&format!("apps/cookbook/api/recipes/{id}", id = rm.id)) .join(&format!("apps/cookbook/api/recipes/{id}", id = rm.id))
.unwrap(), .unwrap(),
) )
@ -113,21 +113,22 @@ where
let calendar_prototype: ics::ICalendar = ics::ICalendar::new( let calendar_prototype: ics::ICalendar = ics::ICalendar::new(
"2.0", "2.0",
format!( format!(
"-//{}//NONSGML {}//EN", "-//IDN {}//{} {}//EN",
constants::VENDOR, constants::VENDOR,
env!("CARGO_PKG_NAME") env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION")
), ),
); );
let dav_base = api_client let dav_base = api_client
.rest .rest()
.head(api_client.base_url.join("/.well-known/caldav")?) .head(api_client.base_url().join("/.well-known/caldav")?)
.send() .send()
.await?; .await?;
let calendar_url = dav_base.url().join(&format!( let calendar_url = dav_base.url().join(&format!(
"calendars/{}/{}/", "calendars/{}/{}/",
&api_client.username, &api_client.username(),
calendar.to_lowercase().as_str().replace(" ", "-") calendar.to_lowercase().as_str().replace(" ", "-")
))?; ))?;
@ -139,7 +140,7 @@ where
cal.add_event(ev.into()); cal.add_event(ev.into());
api_client api_client
.rest .rest()
.put(url) .put(url)
.header("Content-Type", "text/calendar; charset=utf-8") .header("Content-Type", "text/calendar; charset=utf-8")
.body(cal.to_string()) .body(cal.to_string())

View File

@ -48,7 +48,11 @@ impl Event {
impl<'a> From<Event> for CalEvent<'a> { impl<'a> From<Event> for CalEvent<'a> {
fn from(ev: Event) -> Self { fn from(ev: Event) -> Self {
let start_time = ev.ends_at - ev.recipe.total_time(); let start_time = Local
.from_local_datetime(&(*&ev.ends_at - ev.recipe.total_time()))
.unwrap();
let end_time = Local.from_local_datetime(&ev.ends_at).unwrap();
let timezone = iana_time_zone::get_timezone().unwrap();
let mut event = ics::Event::new(ev.uid.clone(), dt_utc_fmt(&Utc::now())); let mut event = ics::Event::new(ev.uid.clone(), dt_utc_fmt(&Utc::now()));
event.push(calprop::Summary::new(escape_text(ev.recipe.name.clone()))); event.push(calprop::Summary::new(escape_text(ev.recipe.name.clone())));
@ -57,18 +61,22 @@ impl<'a> From<Event> for CalEvent<'a> {
ev.recipe.id ev.recipe.id
))); )));
event.push(calprop::Location::new(escape_text(ev.recipe.url.clone()))); event.push(calprop::Location::new(escape_text(ev.recipe.url.clone())));
event.push(calprop::DtStart::new(dt_fmt(
&Local.from_local_datetime(&start_time).unwrap(), let mut dtstart = calprop::DtStart::new(dt_fmt(&start_time));
))); dtstart.append(ics::parameters!("TZID" => timezone.clone()));
event.push(calprop::DtEnd::new(dt_fmt( event.push(dtstart);
&Local.from_local_datetime(&ev.ends_at).unwrap(),
))); let mut dtend = calprop::DtEnd::new(dt_fmt(&end_time));
dtend.append(ics::parameters!("TZID" => timezone));
event.push(dtend);
// TODO make configurable yearly repetition, for now this suits my personal uses // TODO make configurable yearly repetition, for now this suits my personal uses
event.push(calprop::RRule::new("FREQ=YEARLY")); event.push(calprop::RRule::new("FREQ=YEARLY"));
let mut trigger = calprop::Trigger::new("-PT15M");
trigger.append(ics::parameters!("RELATED" => "START"));
let alarm = ics::Alarm::display( let alarm = ics::Alarm::display(
calprop::Trigger::new("-P15M"), trigger,
calprop::Description::new(escape_text(ev.recipe.name.clone())), calprop::Description::new(escape_text(ev.recipe.name.clone())),
); );
event.add_alarm(alarm); event.add_alarm(alarm);