make calendar object id extrinsic

This commit is contained in:
Lennart K
2026-01-07 13:14:50 +01:00
parent 758793a11a
commit a2255bc7f1
17 changed files with 116 additions and 96 deletions

View File

@@ -21,7 +21,7 @@ pub async fn get_objects_calendar_multiget<C: CalendarStore>(
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
store: &C, store: &C,
) -> Result<(Vec<CalendarObject>, Vec<String>), Error> { ) -> Result<(Vec<(String, CalendarObject)>, Vec<String>), Error> {
let mut result = vec![]; let mut result = vec![];
let mut not_found = vec![]; let mut not_found = vec![];
@@ -32,7 +32,7 @@ pub async fn get_objects_calendar_multiget<C: CalendarStore>(
let filename = filename.trim_start_matches('/'); let filename = filename.trim_start_matches('/');
if let Some(object_id) = filename.strip_suffix(".ics") { if let Some(object_id) = filename.strip_suffix(".ics") {
match store.get_object(principal, cal_id, object_id, false).await { match store.get_object(principal, cal_id, object_id, false).await {
Ok(object) => result.push(object), Ok(object) => result.push((object_id.to_owned(), object)),
Err(rustical_store::Error::NotFound) => not_found.push(href.to_string()), Err(rustical_store::Error::NotFound) => not_found.push(href.to_string()),
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
} }

View File

@@ -130,7 +130,7 @@ END:VCALENDAR";
#[test] #[test]
fn test_comp_filter_matching() { fn test_comp_filter_matching() {
let object = CalendarObject::from_ics(ICS.to_string(), None).unwrap(); let object = CalendarObject::from_ics(ICS.to_string()).unwrap();
let comp_filter = CompFilterElement { let comp_filter = CompFilterElement {
is_not_defined: Some(()), is_not_defined: Some(()),
@@ -231,7 +231,7 @@ END:VCALENDAR";
} }
#[test] #[test]
fn test_comp_filter_time_range() { fn test_comp_filter_time_range() {
let object = CalendarObject::from_ics(ICS.to_string(), None).unwrap(); let object = CalendarObject::from_ics(ICS.to_string()).unwrap();
let comp_filter = CompFilterElement { let comp_filter = CompFilterElement {
is_not_defined: None, is_not_defined: None,
@@ -286,7 +286,7 @@ END:VCALENDAR";
#[test] #[test]
fn test_match_timezone() { fn test_match_timezone() {
let object = CalendarObject::from_ics(ICS.to_string(), None).unwrap(); let object = CalendarObject::from_ics(ICS.to_string()).unwrap();
let comp_filter = CompFilterElement { let comp_filter = CompFilterElement {
is_not_defined: None, is_not_defined: None,

View File

@@ -18,12 +18,12 @@ pub async fn get_objects_calendar_query<C: CalendarStore>(
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
store: &C, store: &C,
) -> Result<Vec<CalendarObject>, Error> { ) -> Result<Vec<(String, CalendarObject)>, Error> {
let mut objects = store let mut objects = store
.calendar_query(principal, cal_id, cal_query.into()) .calendar_query(principal, cal_id, cal_query.into())
.await?; .await?;
if let Some(filter) = &cal_query.filter { if let Some(filter) = &cal_query.filter {
objects.retain(|object| filter.matches(object.get_inner())); objects.retain(|(_id, object)| filter.matches(object.get_inner()));
} }
Ok(objects) Ok(objects)
} }

View File

@@ -77,7 +77,7 @@ const FILTER_2: &str = r#"
#[case(ICS_1, FILTER_1, true)] #[case(ICS_1, FILTER_1, true)]
#[case(ICS_1, FILTER_2, false)] #[case(ICS_1, FILTER_2, false)]
fn yeet(#[case] ics: &str, #[case] filter: &str, #[case] matches: bool) { fn yeet(#[case] ics: &str, #[case] filter: &str, #[case] matches: bool) {
let obj = CalendarObject::from_ics(ics.to_owned(), None).unwrap(); let obj = CalendarObject::from_ics(ics.to_owned()).unwrap();
let filter = FilterElement::parse_str(filter).unwrap(); let filter = FilterElement::parse_str(filter).unwrap();
assert_eq!(matches, filter.matches(obj.get_inner())); assert_eq!(matches, filter.matches(obj.get_inner()));
} }

View File

@@ -51,7 +51,7 @@ impl ReportRequest {
} }
fn objects_response( fn objects_response(
objects: Vec<CalendarObject>, objects: Vec<(String, CalendarObject)>,
not_found: Vec<String>, not_found: Vec<String>,
path: &str, path: &str,
principal: &str, principal: &str,
@@ -60,11 +60,12 @@ fn objects_response(
prop: &PropfindType<CalendarObjectPropWrapperName>, prop: &PropfindType<CalendarObjectPropWrapperName>,
) -> Result<MultistatusElement<CalendarObjectPropWrapper, String>, Error> { ) -> Result<MultistatusElement<CalendarObjectPropWrapper, String>, Error> {
let mut responses = Vec::new(); let mut responses = Vec::new();
for object in objects { for (object_id, object) in objects {
let path = format!("{}/{}.ics", path, object.get_id()); let path = format!("{path}/{object_id}.ics");
responses.push( responses.push(
CalendarObjectResource { CalendarObjectResource {
object, object,
object_id,
principal: principal.to_owned(), principal: principal.to_owned(),
} }
.propfind(&path, prop, None, puri, user)?, .propfind(&path, prop, None, puri, user)?,

View File

@@ -32,11 +32,12 @@ pub async fn handle_sync_collection<C: CalendarStore>(
.await?; .await?;
let mut responses = Vec::new(); let mut responses = Vec::new();
for object in new_objects { for (object_id, object) in new_objects {
let path = format!("{}/{}.ics", path, object.get_id()); let path = format!("{}/{}.ics", path, &object_id);
responses.push( responses.push(
CalendarObjectResource { CalendarObjectResource {
object, object,
object_id,
principal: principal.to_owned(), principal: principal.to_owned(),
} }
.propfind(&path, &sync_collection.prop, None, puri, user)?, .propfind(&path, &sync_collection.prop, None, puri, user)?,

View File

@@ -78,8 +78,9 @@ impl<C: CalendarStore, S: SubscriptionStore> ResourceService for CalendarResourc
.get_objects(principal, cal_id) .get_objects(principal, cal_id)
.await? .await?
.into_iter() .into_iter()
.map(|object| CalendarObjectResource { .map(|(object_id, object)| CalendarObjectResource {
object, object,
object_id,
principal: principal.to_owned(), principal: principal.to_owned(),
}) })
.collect()) .collect())
@@ -91,7 +92,7 @@ impl<C: CalendarStore, S: SubscriptionStore> ResourceService for CalendarResourc
file: Self::Resource, file: Self::Resource,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
self.cal_store self.cal_store
.update_calendar(principal.to_owned(), cal_id.to_owned(), file.into()) .update_calendar(principal, cal_id, file.into())
.await?; .await?;
Ok(()) Ok(())
} }

View File

@@ -94,13 +94,13 @@ pub async fn put_event<C: CalendarStore>(
true true
}; };
let Ok(object) = CalendarObject::from_ics(body.clone(), Some(object_id)) else { let Ok(object) = CalendarObject::from_ics(body.clone()) else {
debug!("invalid calendar data:\n{body}"); debug!("invalid calendar data:\n{body}");
return Err(Error::PreconditionFailed(Precondition::ValidCalendarData)); return Err(Error::PreconditionFailed(Precondition::ValidCalendarData));
}; };
let etag = object.get_etag(); let etag = object.get_etag();
cal_store cal_store
.put_object(principal, calendar_id, object, overwrite) .put_object(&principal, &calendar_id, &object_id, object, overwrite)
.await?; .await?;
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();

View File

@@ -1,5 +1,3 @@
use std::borrow::Cow;
use super::prop::{ use super::prop::{
CalendarData, CalendarObjectProp, CalendarObjectPropName, CalendarObjectPropWrapper, CalendarData, CalendarObjectProp, CalendarObjectPropName, CalendarObjectPropWrapper,
CalendarObjectPropWrapperName, CalendarObjectPropWrapperName,
@@ -14,16 +12,18 @@ use rustical_dav::{
}; };
use rustical_ical::CalendarObject; use rustical_ical::CalendarObject;
use rustical_store::auth::Principal; use rustical_store::auth::Principal;
use std::borrow::Cow;
#[derive(Clone, From, Into)] #[derive(Clone, From, Into)]
pub struct CalendarObjectResource { pub struct CalendarObjectResource {
pub object: CalendarObject, pub object: CalendarObject,
pub object_id: String,
pub principal: String, pub principal: String,
} }
impl ResourceName for CalendarObjectResource { impl ResourceName for CalendarObjectResource {
fn get_name(&self) -> Cow<'_, str> { fn get_name(&self) -> Cow<'_, str> {
Cow::from(format!("{}.ics", self.object.get_id())) Cow::from(format!("{}.ics", self.object_id))
} }
} }

View File

@@ -66,6 +66,7 @@ impl<C: CalendarStore> ResourceService for CalendarObjectResourceService<C> {
.await?; .await?;
Ok(CalendarObjectResource { Ok(CalendarObjectResource {
object, object,
object_id: object_id.to_owned(),
principal: principal.to_owned(), principal: principal.to_owned(),
}) })
} }

View File

@@ -62,13 +62,12 @@ impl rustical_xml::ValueDeserialize for CalendarObjectType {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CalendarObject { pub struct CalendarObject {
id: String,
inner: IcalCalendarObject, inner: IcalCalendarObject,
ics: String, ics: String,
} }
impl CalendarObject { impl CalendarObject {
pub fn from_ics(ics: String, id: Option<String>) -> Result<Self, Error> { pub fn from_ics(ics: String) -> Result<Self, Error> {
let mut parser: ComponentParser<_, IcalCalendarObject> = let mut parser: ComponentParser<_, IcalCalendarObject> =
ComponentParser::new(ics.as_bytes()); ComponentParser::new(ics.as_bytes());
let inner = parser.next().ok_or(Error::MissingCalendar)??; let inner = parser.next().ok_or(Error::MissingCalendar)??;
@@ -78,11 +77,7 @@ impl CalendarObject {
)); ));
} }
Ok(Self { Ok(Self { inner, ics })
id: id.unwrap_or_else(|| inner.get_uid().to_owned()),
inner,
ics,
})
} }
#[must_use] #[must_use]
@@ -95,11 +90,6 @@ impl CalendarObject {
self.inner.get_uid() self.inner.get_uid()
} }
#[must_use]
pub fn get_id(&self) -> &str {
&self.id
}
#[must_use] #[must_use]
pub fn get_etag(&self) -> String { pub fn get_etag(&self) -> String {
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();

View File

@@ -25,6 +25,6 @@ END:VCALENDAR
#[test] #[test]
fn parse_calendar_object() { fn parse_calendar_object() {
let object = CalendarObject::from_ics(MULTI_VEVENT.to_string(), None).unwrap(); let object = CalendarObject::from_ics(MULTI_VEVENT.to_string()).unwrap();
object.get_inner().expand_recurrence(None, None); object.get_inner().expand_recurrence(None, None);
} }

View File

@@ -22,8 +22,8 @@ pub trait CalendarStore: Send + Sync + 'static {
async fn update_calendar( async fn update_calendar(
&self, &self,
principal: String, principal: &str,
id: String, id: &str,
calendar: Calendar, calendar: Calendar,
) -> Result<(), Error>; ) -> Result<(), Error>;
async fn insert_calendar(&self, calendar: Calendar) -> Result<(), Error>; async fn insert_calendar(&self, calendar: Calendar) -> Result<(), Error>;
@@ -46,7 +46,7 @@ pub trait CalendarStore: Send + Sync + 'static {
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
synctoken: i64, synctoken: i64,
) -> Result<(Vec<CalendarObject>, Vec<String>, i64), Error>; ) -> Result<(Vec<(String, CalendarObject)>, Vec<String>, i64), Error>;
/// Since the <calendar-query> rules are rather complex this function /// Since the <calendar-query> rules are rather complex this function
/// is only meant to do some prefiltering /// is only meant to do some prefiltering
@@ -55,7 +55,7 @@ pub trait CalendarStore: Send + Sync + 'static {
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
_query: CalendarQuery, _query: CalendarQuery,
) -> Result<Vec<CalendarObject>, Error> { ) -> Result<Vec<(String, CalendarObject)>, Error> {
self.get_objects(principal, cal_id).await self.get_objects(principal, cal_id).await
} }
@@ -69,7 +69,7 @@ pub trait CalendarStore: Send + Sync + 'static {
&self, &self,
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
) -> Result<Vec<CalendarObject>, Error>; ) -> Result<Vec<(String, CalendarObject)>, Error>;
async fn get_object( async fn get_object(
&self, &self,
principal: &str, principal: &str,
@@ -79,19 +79,25 @@ pub trait CalendarStore: Send + Sync + 'static {
) -> Result<CalendarObject, Error>; ) -> Result<CalendarObject, Error>;
async fn put_objects( async fn put_objects(
&self, &self,
principal: String, principal: &str,
cal_id: String, cal_id: &str,
objects: Vec<CalendarObject>, objects: Vec<(String, CalendarObject)>,
overwrite: bool, overwrite: bool,
) -> Result<(), Error>; ) -> Result<(), Error>;
async fn put_object( async fn put_object(
&self, &self,
principal: String, principal: &str,
cal_id: String, cal_id: &str,
object_id: &str,
object: CalendarObject, object: CalendarObject,
overwrite: bool, overwrite: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
self.put_objects(principal, cal_id, vec![object], overwrite) self.put_objects(
principal,
cal_id,
vec![(object_id.to_owned(), object)],
overwrite,
)
.await .await
} }
async fn delete_object( async fn delete_object(

View File

@@ -1,5 +1,6 @@
use crate::CalendarStore; use crate::{Calendar, CalendarStore, calendar_store::CalendarQuery};
use async_trait::async_trait; use async_trait::async_trait;
use rustical_ical::CalendarObject;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
pub trait PrefixedCalendarStore: CalendarStore { pub trait PrefixedCalendarStore: CalendarStore {
@@ -51,11 +52,11 @@ impl CalendarStore for CombinedCalendarStore {
async fn update_calendar( async fn update_calendar(
&self, &self,
principal: String, principal: &str,
id: String, id: &str,
calendar: crate::Calendar, calendar: Calendar,
) -> Result<(), crate::Error> { ) -> Result<(), crate::Error> {
self.store_for_id(&id) self.store_for_id(id)
.update_calendar(principal, id, calendar) .update_calendar(principal, id, calendar)
.await .await
} }
@@ -88,7 +89,7 @@ impl CalendarStore for CombinedCalendarStore {
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
synctoken: i64, synctoken: i64,
) -> Result<(Vec<rustical_ical::CalendarObject>, Vec<String>, i64), crate::Error> { ) -> Result<(Vec<(String, CalendarObject)>, Vec<String>, i64), crate::Error> {
self.store_for_id(cal_id) self.store_for_id(cal_id)
.sync_changes(principal, cal_id, synctoken) .sync_changes(principal, cal_id, synctoken)
.await .await
@@ -97,7 +98,7 @@ impl CalendarStore for CombinedCalendarStore {
async fn import_calendar( async fn import_calendar(
&self, &self,
calendar: crate::Calendar, calendar: crate::Calendar,
objects: Vec<rustical_ical::CalendarObject>, objects: Vec<CalendarObject>,
merge_existing: bool, merge_existing: bool,
) -> Result<(), crate::Error> { ) -> Result<(), crate::Error> {
self.store_for_id(&calendar.id) self.store_for_id(&calendar.id)
@@ -109,8 +110,8 @@ impl CalendarStore for CombinedCalendarStore {
&self, &self,
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
query: crate::calendar_store::CalendarQuery, query: CalendarQuery,
) -> Result<Vec<rustical_ical::CalendarObject>, crate::Error> { ) -> Result<Vec<(String, CalendarObject)>, crate::Error> {
self.store_for_id(cal_id) self.store_for_id(cal_id)
.calendar_query(principal, cal_id, query) .calendar_query(principal, cal_id, query)
.await .await
@@ -141,7 +142,7 @@ impl CalendarStore for CombinedCalendarStore {
&self, &self,
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
) -> Result<Vec<rustical_ical::CalendarObject>, crate::Error> { ) -> Result<Vec<(String, CalendarObject)>, crate::Error> {
self.store_for_id(cal_id) self.store_for_id(cal_id)
.get_objects(principal, cal_id) .get_objects(principal, cal_id)
.await .await
@@ -149,12 +150,12 @@ impl CalendarStore for CombinedCalendarStore {
async fn put_objects( async fn put_objects(
&self, &self,
principal: String, principal: &str,
cal_id: String, cal_id: &str,
objects: Vec<rustical_ical::CalendarObject>, objects: Vec<(String, CalendarObject)>,
overwrite: bool, overwrite: bool,
) -> Result<(), crate::Error> { ) -> Result<(), crate::Error> {
self.store_for_id(&cal_id) self.store_for_id(cal_id)
.put_objects(principal, cal_id, objects, overwrite) .put_objects(principal, cal_id, objects, overwrite)
.await .await
} }

View File

@@ -34,16 +34,19 @@ fn benchmark(c: &mut Criterion) {
cal_store cal_store
}); });
let object = CalendarObject::from_ics(include_str!("ical_event.ics").to_owned(), None).unwrap(); let row = (
"asd".to_owned(),
CalendarObject::from_ics(include_str!("ical_event.ics").to_owned()).unwrap(),
);
let batch_size = 1000; let batch_size = 1000;
let objects: Vec<_> = std::iter::repeat_n(object.clone(), batch_size).collect(); let objects: Vec<_> = std::iter::repeat_n(row.clone(), batch_size).collect();
c.bench_function("put_batch", |b| { c.bench_function("put_batch", |b| {
b.to_async(&runtime).iter(async || { b.to_async(&runtime).iter(async || {
// yeet // yeet
cal_store cal_store
.put_objects("user".to_owned(), "okwow".to_owned(), objects.clone(), true) .put_objects("user", "okwow", objects.clone(), true)
.await .await
.unwrap(); .unwrap();
}); });
@@ -54,7 +57,7 @@ fn benchmark(c: &mut Criterion) {
// yeet // yeet
for _ in 0..1000 { for _ in 0..1000 {
cal_store cal_store
.put_object("user".to_owned(), "okwow".to_owned(), object.clone(), true) .put_object("user", "okwow", &row.0, row.1.clone(), true)
.await .await
.unwrap(); .unwrap();
} }

View File

@@ -1,7 +1,7 @@
use crate::addressbook_store::SqliteAddressbookStore; use crate::addressbook_store::SqliteAddressbookStore;
use async_trait::async_trait; use async_trait::async_trait;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use rustical_ical::{AddressObject, CalendarObject, CalendarObjectType}; use rustical_ical::{CalendarObject, CalendarObjectType};
use rustical_store::{ use rustical_store::{
Addressbook, AddressbookStore, Calendar, CalendarMetadata, CalendarStore, CollectionMetadata, Addressbook, AddressbookStore, Calendar, CalendarMetadata, CalendarStore, CollectionMetadata,
Error, PrefixedCalendarStore, Error, PrefixedCalendarStore,
@@ -268,10 +268,11 @@ impl CalendarStore for SqliteAddressbookStore {
#[instrument] #[instrument]
async fn update_calendar( async fn update_calendar(
&self, &self,
principal: String, principal: &str,
id: String, id: &str,
mut calendar: Calendar, mut calendar: Calendar,
) -> Result<(), Error> { ) -> Result<(), Error> {
assert_eq!(principal, calendar.principal);
assert_eq!(id, calendar.id); assert_eq!(id, calendar.id);
calendar.id = calendar calendar.id = calendar
.id .id
@@ -323,7 +324,7 @@ impl CalendarStore for SqliteAddressbookStore {
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
synctoken: i64, synctoken: i64,
) -> Result<(Vec<CalendarObject>, Vec<String>, i64), Error> { ) -> Result<(Vec<(String, CalendarObject)>, Vec<String>, i64), Error> {
let cal_id = cal_id let cal_id = cal_id
.strip_prefix(BIRTHDAYS_PREFIX) .strip_prefix(BIRTHDAYS_PREFIX)
.ok_or(Error::NotFound)?; .ok_or(Error::NotFound)?;
@@ -356,7 +357,7 @@ impl CalendarStore for SqliteAddressbookStore {
&self, &self,
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
) -> Result<Vec<CalendarObject>, Error> { ) -> Result<Vec<(String, CalendarObject)>, Error> {
todo!() todo!()
// let cal_id = cal_id // let cal_id = cal_id
// .strip_prefix(BIRTHDAYS_PREFIX) // .strip_prefix(BIRTHDAYS_PREFIX)
@@ -397,9 +398,9 @@ impl CalendarStore for SqliteAddressbookStore {
#[instrument] #[instrument]
async fn put_objects( async fn put_objects(
&self, &self,
_principal: String, _principal: &str,
_cal_id: String, _cal_id: &str,
_objects: Vec<CalendarObject>, _objects: Vec<(String, CalendarObject)>,
_overwrite: bool, _overwrite: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
Err(Error::ReadOnly) Err(Error::ReadOnly)

View File

@@ -22,11 +22,11 @@ struct CalendarObjectRow {
uid: String, uid: String,
} }
impl TryFrom<CalendarObjectRow> for CalendarObject { impl TryFrom<CalendarObjectRow> for (String, CalendarObject) {
type Error = rustical_store::Error; type Error = rustical_store::Error;
fn try_from(value: CalendarObjectRow) -> Result<Self, Self::Error> { fn try_from(value: CalendarObjectRow) -> Result<Self, Self::Error> {
let object = Self::from_ics(value.ics, Some(value.id))?; let object = CalendarObject::from_ics(value.ics)?;
if object.get_uid() != value.uid { if object.get_uid() != value.uid {
return Err(rustical_store::Error::IcalError( return Err(rustical_store::Error::IcalError(
rustical_ical::Error::InvalidData(format!( rustical_ical::Error::InvalidData(format!(
@@ -36,7 +36,7 @@ impl TryFrom<CalendarObjectRow> for CalendarObject {
)), )),
)); ));
} }
Ok(object) Ok((value.id, object))
} }
} }
@@ -358,8 +358,8 @@ impl SqliteCalendarStore {
async fn _update_calendar<'e, E: Executor<'e, Database = Sqlite>>( async fn _update_calendar<'e, E: Executor<'e, Database = Sqlite>>(
executor: E, executor: E,
principal: String, principal: &str,
id: String, id: &str,
calendar: Calendar, calendar: Calendar,
) -> Result<(), Error> { ) -> Result<(), Error> {
let comp_event = calendar.components.contains(&CalendarObjectType::Event); let comp_event = calendar.components.contains(&CalendarObjectType::Event);
@@ -457,7 +457,7 @@ impl SqliteCalendarStore {
executor: E, executor: E,
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
) -> Result<Vec<CalendarObject>, Error> { ) -> Result<Vec<(String, CalendarObject)>, Error> {
sqlx::query_as!( sqlx::query_as!(
CalendarObjectRow, CalendarObjectRow,
"SELECT id, uid, ics FROM calendarobjects WHERE principal = ? AND cal_id = ? AND deleted_at IS NULL", "SELECT id, uid, ics FROM calendarobjects WHERE principal = ? AND cal_id = ? AND deleted_at IS NULL",
@@ -476,7 +476,7 @@ impl SqliteCalendarStore {
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
query: CalendarQuery, query: CalendarQuery,
) -> Result<Vec<CalendarObject>, Error> { ) -> Result<Vec<(String, CalendarObject)>, Error> {
// We extend our query interval by one day in each direction since we really don't want to // We extend our query interval by one day in each direction since we really don't want to
// miss any objects because of timezone differences // miss any objects because of timezone differences
// I've previously tried NaiveDate::MIN,MAX, but it seems like sqlite cannot handle these // I've previously tried NaiveDate::MIN,MAX, but it seems like sqlite cannot handle these
@@ -512,7 +512,7 @@ impl SqliteCalendarStore {
object_id: &str, object_id: &str,
show_deleted: bool, show_deleted: bool,
) -> Result<CalendarObject, Error> { ) -> Result<CalendarObject, Error> {
sqlx::query_as!( let (row_id, object) = sqlx::query_as!(
CalendarObjectRow, CalendarObjectRow,
"SELECT id, uid, ics FROM calendarobjects WHERE (principal, cal_id, id) = (?, ?, ?) AND ((deleted_at IS NULL) OR ?)", "SELECT id, uid, ics FROM calendarobjects WHERE (principal, cal_id, id) = (?, ?, ?) AND ((deleted_at IS NULL) OR ?)",
principal, principal,
@@ -523,7 +523,9 @@ impl SqliteCalendarStore {
.fetch_one(executor) .fetch_one(executor)
.await .await
.map_err(crate::Error::from)? .map_err(crate::Error::from)?
.try_into() .try_into()?;
assert_eq!(object_id, row_id);
Ok(object)
} }
#[instrument] #[instrument]
@@ -531,10 +533,11 @@ impl SqliteCalendarStore {
executor: E, executor: E,
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
object_id: &str,
object: &CalendarObject, object: &CalendarObject,
overwrite: bool, overwrite: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
let (object_id, uid, ics) = (object.get_id(), object.get_uid(), object.get_ics()); let (uid, ics) = (object.get_uid(), object.get_ics());
let first_occurence = object let first_occurence = object
.get_inner() .get_inner()
@@ -640,7 +643,7 @@ impl SqliteCalendarStore {
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
synctoken: i64, synctoken: i64,
) -> Result<(Vec<CalendarObject>, Vec<String>, i64), Error> { ) -> Result<(Vec<(String, CalendarObject)>, Vec<String>, i64), Error> {
struct Row { struct Row {
object_id: String, object_id: String,
synctoken: i64, synctoken: i64,
@@ -667,7 +670,7 @@ impl SqliteCalendarStore {
for Row { object_id, .. } in changes { for Row { object_id, .. } in changes {
match Self::_get_object(&mut *conn, principal, cal_id, &object_id, false).await { match Self::_get_object(&mut *conn, principal, cal_id, &object_id, false).await {
Ok(object) => objects.push(object), Ok(object) => objects.push((object_id, object)),
Err(rustical_store::Error::NotFound) => deleted_objects.push(object_id), Err(rustical_store::Error::NotFound) => deleted_objects.push(object_id),
Err(err) => return Err(err), Err(err) => return Err(err),
} }
@@ -707,8 +710,8 @@ impl CalendarStore for SqliteCalendarStore {
#[instrument] #[instrument]
async fn update_calendar( async fn update_calendar(
&self, &self,
principal: String, principal: &str,
id: String, id: &str,
calendar: Calendar, calendar: Calendar,
) -> Result<(), Error> { ) -> Result<(), Error> {
Self::_update_calendar(&self.db, principal, id, calendar).await Self::_update_calendar(&self.db, principal, id, calendar).await
@@ -776,14 +779,23 @@ impl CalendarStore for SqliteCalendarStore {
let mut sync_token = None; let mut sync_token = None;
for object in objects { for object in objects {
Self::_put_object(&mut *tx, &calendar.principal, &calendar.id, &object, false).await?; let object_id = object.get_uid();
Self::_put_object(
&mut *tx,
&calendar.principal,
&calendar.id,
object_id,
&object,
false,
)
.await?;
sync_token = Some( sync_token = Some(
Self::log_object_operation( Self::log_object_operation(
&mut tx, &mut tx,
&calendar.principal, &calendar.principal,
&calendar.id, &calendar.id,
object.get_id(), object_id,
ChangeOperation::Add, ChangeOperation::Add,
) )
.await?, .await?,
@@ -809,7 +821,7 @@ impl CalendarStore for SqliteCalendarStore {
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
query: CalendarQuery, query: CalendarQuery,
) -> Result<Vec<CalendarObject>, Error> { ) -> Result<Vec<(String, CalendarObject)>, Error> {
Self::_calendar_query(&self.db, principal, cal_id, query).await Self::_calendar_query(&self.db, principal, cal_id, query).await
} }
@@ -840,7 +852,7 @@ impl CalendarStore for SqliteCalendarStore {
&self, &self,
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
) -> Result<Vec<CalendarObject>, Error> { ) -> Result<Vec<(String, CalendarObject)>, Error> {
Self::_get_objects(&self.db, principal, cal_id).await Self::_get_objects(&self.db, principal, cal_id).await
} }
@@ -858,9 +870,9 @@ impl CalendarStore for SqliteCalendarStore {
#[instrument] #[instrument]
async fn put_objects( async fn put_objects(
&self, &self,
principal: String, principal: &str,
cal_id: String, cal_id: &str,
objects: Vec<CalendarObject>, objects: Vec<(String, CalendarObject)>,
overwrite: bool, overwrite: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut tx = self let mut tx = self
@@ -876,18 +888,21 @@ impl CalendarStore for SqliteCalendarStore {
} }
let mut sync_token = None; let mut sync_token = None;
for object in objects { for (object_id, object) in objects {
sync_token = Some( sync_token = Some(
Self::log_object_operation( Self::log_object_operation(
&mut tx, &mut tx,
&principal, &principal,
&cal_id, &cal_id,
object.get_id(), &object_id,
ChangeOperation::Add, ChangeOperation::Add,
) )
.await?, .await?,
); );
Self::_put_object(&mut *tx, &principal, &cal_id, &object, overwrite).await?; Self::_put_object(
&mut *tx, &principal, &cal_id, &object_id, &object, overwrite,
)
.await?;
} }
tx.commit().await.map_err(crate::Error::from)?; tx.commit().await.map_err(crate::Error::from)?;
@@ -965,7 +980,7 @@ impl CalendarStore for SqliteCalendarStore {
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
synctoken: i64, synctoken: i64,
) -> Result<(Vec<CalendarObject>, Vec<String>, i64), Error> { ) -> Result<(Vec<(String, CalendarObject)>, Vec<String>, i64), Error> {
Self::_sync_changes(&self.db, principal, cal_id, synctoken).await Self::_sync_changes(&self.db, principal, cal_id, synctoken).await
} }