mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-26 11:09:22 +00:00
Compare commits
5 Commits
v0.11.4
...
b50ea478db
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b50ea478db | ||
|
|
2c7748255c | ||
|
|
f40a23a1f1 | ||
|
|
2a4ba33e45 | ||
|
|
6bc4bd3fa3 |
54
Cargo.lock
generated
54
Cargo.lock
generated
@@ -156,9 +156,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "askama_web"
|
||||
version = "0.14.6"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50dcd7d2caaff31b91ef5d112ed10416344e23a33db9e7eea7ba695d2a97a88a"
|
||||
checksum = "e1acadd534892f9ef8c3809b47997e3cd857fad735edceff77a88be1c8236920"
|
||||
dependencies = [
|
||||
"askama",
|
||||
"askama_web_derive",
|
||||
@@ -1641,7 +1641,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ical"
|
||||
version = "0.11.0"
|
||||
source = "git+https://github.com/lennart-k/ical-rs#211ce20acf3ecc7831eecf78d9a23232b83d7a6c"
|
||||
source = "git+https://github.com/lennart-k/ical-rs#8698980a24cbf5dbf711f3edbb8a3ea9540b89eb"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
@@ -1791,14 +1791,15 @@ checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.44.3"
|
||||
version = "1.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5c943d4415edd8153251b6f197de5eb1640e56d84e8d9159bea190421c73698"
|
||||
checksum = "b76866be74d68b1595eb8060cb9191dca9c021db2316558e52ddc5d55d41b66c"
|
||||
dependencies = [
|
||||
"console",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"similar",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3101,7 +3102,7 @@ dependencies = [
|
||||
"serde",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"toml 0.9.9+spec-1.0.0",
|
||||
"toml 0.9.10+spec-1.1.0",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-sessions",
|
||||
@@ -4033,6 +4034,19 @@ dependencies = [
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
@@ -4215,14 +4229,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.9+spec-1.0.0"
|
||||
version = "0.9.10+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb5238e643fc34a1d5d7e753e1532a91912d74b63b92b3ea51fde8d1b7bc79dd"
|
||||
checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48"
|
||||
dependencies = [
|
||||
"indexmap 2.12.1",
|
||||
"serde_core",
|
||||
"serde_spanned 1.0.4",
|
||||
"toml_datetime 0.7.4+spec-1.0.0",
|
||||
"toml_datetime 0.7.5+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
@@ -4239,9 +4253,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.4+spec-1.0.0"
|
||||
version = "0.7.5+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe3cea6b2aa3b910092f6abd4053ea464fab5f9c170ba5e9a6aead16ec4af2b6"
|
||||
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
@@ -4267,16 +4281,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
|
||||
dependencies = [
|
||||
"indexmap 2.12.1",
|
||||
"toml_datetime 0.7.4+spec-1.0.0",
|
||||
"toml_datetime 0.7.5+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.5+spec-1.0.0"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c03bee5ce3696f31250db0bbaff18bc43301ce0e8db2ed1f07cbb2acf89984c"
|
||||
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
@@ -4289,9 +4303,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.5+spec-1.0.0"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9cd6190959dce0994aa8970cd32ab116d1851ead27e866039acaf2524ce44fa"
|
||||
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
|
||||
|
||||
[[package]]
|
||||
name = "tonic"
|
||||
@@ -4458,9 +4472,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.43"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
@@ -4481,9 +4495,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.35"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
|
||||
@@ -109,7 +109,6 @@ strum_macros = "0.27"
|
||||
serde_json = { version = "1.0", features = ["raw_value"] }
|
||||
sqlx-sqlite = { version = "0.8", features = ["bundled"] }
|
||||
ical = { git = "https://github.com/lennart-k/ical-rs", features = [
|
||||
"generator",
|
||||
"serde",
|
||||
"chrono-tz",
|
||||
] }
|
||||
|
||||
@@ -5,7 +5,8 @@ use axum::extract::State;
|
||||
use axum::{extract::Path, response::Response};
|
||||
use headers::{ContentType, HeaderMapExt};
|
||||
use http::{HeaderValue, Method, StatusCode, header};
|
||||
use ical::generator::{Emitter, IcalCalendarBuilder};
|
||||
use ical::builder::calendar::IcalCalendarBuilder;
|
||||
use ical::generator::Emitter;
|
||||
use ical::property::Property;
|
||||
use percent_encoding::{CONTROLS, utf8_percent_encode};
|
||||
use rustical_ical::{CalendarObjectComponent, EventObject};
|
||||
@@ -35,7 +36,7 @@ pub async fn route_get<C: CalendarStore, S: SubscriptionStore>(
|
||||
let mut vtimezones = HashMap::new();
|
||||
let objects = cal_store.get_objects(&principal, &calendar_id).await?;
|
||||
|
||||
let mut ical_calendar_builder = IcalCalendarBuilder::version("4.0")
|
||||
let mut ical_calendar_builder = IcalCalendarBuilder::version("2.0")
|
||||
.gregorian()
|
||||
.prodid("RustiCal");
|
||||
if let Some(displayname) = calendar.meta.displayname {
|
||||
@@ -64,30 +65,24 @@ pub async fn route_get<C: CalendarStore, S: SubscriptionStore>(
|
||||
vtimezones.extend(object.get_vtimezones());
|
||||
match object.get_data() {
|
||||
CalendarObjectComponent::Event(EventObject { event, .. }, overrides) => {
|
||||
ical_calendar_builder = ical_calendar_builder.add_event(event.clone());
|
||||
for ev_override in overrides {
|
||||
ical_calendar_builder =
|
||||
ical_calendar_builder.add_event(ev_override.event.clone());
|
||||
}
|
||||
ical_calendar_builder = ical_calendar_builder
|
||||
.add_event(event.clone())
|
||||
.add_events(overrides.iter().map(|ev| ev.event.clone()));
|
||||
}
|
||||
CalendarObjectComponent::Todo(todo, overrides) => {
|
||||
ical_calendar_builder = ical_calendar_builder.add_todo(todo.clone());
|
||||
for ev_override in overrides {
|
||||
ical_calendar_builder = ical_calendar_builder.add_todo(ev_override.clone());
|
||||
}
|
||||
ical_calendar_builder = ical_calendar_builder
|
||||
.add_todo(todo.clone())
|
||||
.add_todos(overrides.iter().cloned());
|
||||
}
|
||||
CalendarObjectComponent::Journal(journal, overrides) => {
|
||||
ical_calendar_builder = ical_calendar_builder.add_journal(journal.clone());
|
||||
for ev_override in overrides {
|
||||
ical_calendar_builder = ical_calendar_builder.add_journal(ev_override.clone());
|
||||
}
|
||||
ical_calendar_builder = ical_calendar_builder
|
||||
.add_journal(journal.clone())
|
||||
.add_journals(overrides.iter().cloned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for vtimezone in vtimezones.into_values() {
|
||||
ical_calendar_builder = ical_calendar_builder.add_tz(vtimezone.to_owned());
|
||||
}
|
||||
ical_calendar_builder = ical_calendar_builder.add_timezones(vtimezones.into_values().cloned());
|
||||
|
||||
let ical_calendar = ical_calendar_builder
|
||||
.build()
|
||||
@@ -95,7 +90,7 @@ pub async fn route_get<C: CalendarStore, S: SubscriptionStore>(
|
||||
|
||||
let mut resp = Response::builder().status(StatusCode::OK);
|
||||
let hdrs = resp.headers_mut().unwrap();
|
||||
hdrs.typed_insert(ContentType::from_str("text/calendar").unwrap());
|
||||
hdrs.typed_insert(ContentType::from_str("text/calendar; charset=utf-8").unwrap());
|
||||
|
||||
let filename = format!("{}_{}.ics", calendar.principal, calendar.id);
|
||||
let filename = utf8_percent_encode(&filename, CONTROLS);
|
||||
|
||||
@@ -42,7 +42,7 @@ pub async fn get_event<C: CalendarStore>(
|
||||
let mut resp = Response::builder().status(StatusCode::OK);
|
||||
let hdrs = resp.headers_mut().unwrap();
|
||||
hdrs.typed_insert(ETag::from_str(&event.get_etag()).unwrap());
|
||||
hdrs.typed_insert(ContentType::from_str("text/calendar").unwrap());
|
||||
hdrs.typed_insert(ContentType::from_str("text/calendar; charset=utf-8").unwrap());
|
||||
if matches!(method, Method::HEAD) {
|
||||
Ok(resp.body(Body::empty()).unwrap())
|
||||
} else {
|
||||
|
||||
@@ -50,7 +50,7 @@ pub async fn get_object<AS: AddressbookStore>(
|
||||
let mut resp = Response::builder().status(StatusCode::OK);
|
||||
let hdrs = resp.headers_mut().unwrap();
|
||||
hdrs.typed_insert(ETag::from_str(&object.get_etag()).unwrap());
|
||||
hdrs.typed_insert(ContentType::from_str("text/vcard").unwrap());
|
||||
hdrs.typed_insert(ContentType::from_str("text/vcard; charset=utf-8").unwrap());
|
||||
if matches!(method, Method::HEAD) {
|
||||
Ok(resp.body(Body::empty()).unwrap())
|
||||
} else {
|
||||
|
||||
@@ -46,7 +46,7 @@ pub async fn route_get<AS: AddressbookStore, S: SubscriptionStore>(
|
||||
|
||||
let mut resp = Response::builder().status(StatusCode::OK);
|
||||
let hdrs = resp.headers_mut().unwrap();
|
||||
hdrs.typed_insert(ContentType::from_str("text/vcard").unwrap());
|
||||
hdrs.typed_insert(ContentType::from_str("text/vcard; charset=utf-8").unwrap());
|
||||
let filename = format!("{principal}_{addressbook_id}.vcf");
|
||||
let filename = utf8_percent_encode(&filename, CONTROLS);
|
||||
hdrs.insert(
|
||||
|
||||
@@ -99,85 +99,85 @@ impl EventObject {
|
||||
end: Option<DateTime<Utc>>,
|
||||
overrides: &[Self],
|
||||
) -> Result<Vec<IcalEvent>, Error> {
|
||||
if let Some(mut rrule_set) = self.recurrence_ruleset()? {
|
||||
if let Some(start) = start {
|
||||
rrule_set = rrule_set.after(start.with_timezone(&rrule::Tz::UTC));
|
||||
}
|
||||
if let Some(end) = end {
|
||||
rrule_set = rrule_set.before(end.with_timezone(&rrule::Tz::UTC));
|
||||
}
|
||||
let mut events = vec![];
|
||||
let dates = rrule_set.all(2048).dates;
|
||||
let dtstart = self.get_dtstart()?.expect("We must have a DTSTART here");
|
||||
let computed_duration = self
|
||||
.get_dtend()?
|
||||
.map(|dtend| dtend.as_datetime().into_owned() - dtstart.as_datetime().into_owned());
|
||||
let Some(mut rrule_set) = self.recurrence_ruleset()? else {
|
||||
return Ok(vec![self.event.clone()]);
|
||||
};
|
||||
|
||||
'recurrence: for date in dates {
|
||||
let date = CalDateTime::from(date);
|
||||
let dateformat = if dtstart.is_date() {
|
||||
date.format_date()
|
||||
} else {
|
||||
date.format()
|
||||
};
|
||||
|
||||
for ev_override in overrides {
|
||||
if let Some(override_id) = &ev_override
|
||||
.event
|
||||
.get_recurrence_id()
|
||||
.as_ref()
|
||||
.expect("overrides have a recurrence id")
|
||||
.value
|
||||
&& override_id == &dateformat
|
||||
{
|
||||
// We have an override for this occurence
|
||||
//
|
||||
events.push(ev_override.event.clone());
|
||||
continue 'recurrence;
|
||||
}
|
||||
}
|
||||
|
||||
let mut ev = self.event.clone().mutable();
|
||||
ev.remove_property("RRULE");
|
||||
ev.remove_property("RDATE");
|
||||
ev.remove_property("EXDATE");
|
||||
ev.remove_property("EXRULE");
|
||||
let dtstart_prop = ev
|
||||
.get_property("DTSTART")
|
||||
.expect("We must have a DTSTART here")
|
||||
.clone();
|
||||
ev.remove_property("DTSTART");
|
||||
ev.remove_property("DTEND");
|
||||
|
||||
ev.set_property(Property {
|
||||
name: "RECURRENCE-ID".to_string(),
|
||||
value: Some(dateformat.clone()),
|
||||
params: vec![],
|
||||
});
|
||||
ev.set_property(Property {
|
||||
name: "DTSTART".to_string(),
|
||||
value: Some(dateformat),
|
||||
params: dtstart_prop.params.clone(),
|
||||
});
|
||||
if let Some(duration) = computed_duration {
|
||||
let dtend = date + duration;
|
||||
let dtendformat = if dtstart.is_date() {
|
||||
dtend.format_date()
|
||||
} else {
|
||||
dtend.format()
|
||||
};
|
||||
ev.set_property(Property {
|
||||
name: "DTEND".to_string(),
|
||||
value: Some(dtendformat),
|
||||
params: dtstart_prop.params,
|
||||
});
|
||||
}
|
||||
events.push(ev.verify()?);
|
||||
}
|
||||
Ok(events)
|
||||
} else {
|
||||
Ok(vec![self.event.clone()])
|
||||
if let Some(start) = start {
|
||||
rrule_set = rrule_set.after(start.with_timezone(&rrule::Tz::UTC));
|
||||
}
|
||||
if let Some(end) = end {
|
||||
rrule_set = rrule_set.before(end.with_timezone(&rrule::Tz::UTC));
|
||||
}
|
||||
let mut events = vec![];
|
||||
let dates = rrule_set.all(2048).dates;
|
||||
let dtstart = self.get_dtstart()?.expect("We must have a DTSTART here");
|
||||
let computed_duration = self
|
||||
.get_dtend()?
|
||||
.map(|dtend| dtend.as_datetime().into_owned() - dtstart.as_datetime().as_ref());
|
||||
|
||||
'recurrence: for date in dates {
|
||||
let date = CalDateTime::from(date);
|
||||
let dateformat = if dtstart.is_date() {
|
||||
date.format_date()
|
||||
} else {
|
||||
date.format()
|
||||
};
|
||||
|
||||
for ev_override in overrides {
|
||||
if let Some(override_id) = &ev_override
|
||||
.event
|
||||
.get_recurrence_id()
|
||||
.as_ref()
|
||||
.expect("overrides have a recurrence id")
|
||||
.value
|
||||
&& override_id == &dateformat
|
||||
{
|
||||
// We have an override for this occurence
|
||||
//
|
||||
events.push(ev_override.event.clone());
|
||||
continue 'recurrence;
|
||||
}
|
||||
}
|
||||
|
||||
let mut ev = self.event.clone().mutable();
|
||||
ev.remove_property("RRULE");
|
||||
ev.remove_property("RDATE");
|
||||
ev.remove_property("EXDATE");
|
||||
ev.remove_property("EXRULE");
|
||||
let dtstart_prop = ev
|
||||
.get_property("DTSTART")
|
||||
.expect("We must have a DTSTART here")
|
||||
.clone();
|
||||
ev.remove_property("DTSTART");
|
||||
ev.remove_property("DTEND");
|
||||
|
||||
ev.set_property(Property {
|
||||
name: "RECURRENCE-ID".to_string(),
|
||||
value: Some(dateformat.clone()),
|
||||
params: vec![],
|
||||
});
|
||||
ev.set_property(Property {
|
||||
name: "DTSTART".to_string(),
|
||||
value: Some(dateformat),
|
||||
params: dtstart_prop.params.clone(),
|
||||
});
|
||||
if let Some(duration) = computed_duration {
|
||||
let dtend = date + duration;
|
||||
let dtendformat = if dtstart.is_date() {
|
||||
dtend.format_date()
|
||||
} else {
|
||||
dtend.format()
|
||||
};
|
||||
ev.set_property(Property {
|
||||
name: "DTEND".to_string(),
|
||||
value: Some(dtendformat),
|
||||
params: dtstart_prop.params,
|
||||
});
|
||||
}
|
||||
events.push(ev.verify()?);
|
||||
}
|
||||
Ok(events)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ source: src/integration_tests/caldav/calendar.rs
|
||||
expression: body
|
||||
---
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:4.0
|
||||
VERSION:2.0
|
||||
CALSCALE:GREGORIAN
|
||||
PRODID:RustiCal
|
||||
X-WR-CALNAME:Calendar
|
||||
|
||||
Reference in New Issue
Block a user