diff --git a/crates/carddav/src/address_object/methods.rs b/crates/carddav/src/address_object/methods.rs index a93c78d..ceabfd3 100644 --- a/crates/carddav/src/address_object/methods.rs +++ b/crates/carddav/src/address_object/methods.rs @@ -103,10 +103,10 @@ pub async fn put_object( true }; - let object = AddressObject::from_vcf(object_id, body)?; + let object = AddressObject::from_vcf(body)?; let etag = object.get_etag(); addr_store - .put_object(principal, addressbook_id, object, overwrite) + .put_object(&principal, &addressbook_id, &object_id, object, overwrite) .await?; let mut headers = HeaderMap::new(); diff --git a/crates/carddav/src/address_object/resource.rs b/crates/carddav/src/address_object/resource.rs index cddcee3..ed5f7f0 100644 --- a/crates/carddav/src/address_object/resource.rs +++ b/crates/carddav/src/address_object/resource.rs @@ -21,11 +21,12 @@ use rustical_store::auth::Principal; pub struct AddressObjectResource { pub object: AddressObject, pub principal: String, + pub object_id: String, } impl ResourceName for AddressObjectResource { fn get_name(&self) -> Cow<'_, str> { - Cow::from(format!("{}.vcf", self.object.get_id())) + Cow::from(format!("{}.vcf", self.object_id)) } } diff --git a/crates/carddav/src/address_object/service.rs b/crates/carddav/src/address_object/service.rs index 1c6efdc..0070a2d 100644 --- a/crates/carddav/src/address_object/service.rs +++ b/crates/carddav/src/address_object/service.rs @@ -57,6 +57,7 @@ impl ResourceService for AddressObjectResourceService .await?; Ok(AddressObjectResource { object, + object_id: object_id.to_owned(), principal: principal.to_owned(), }) } diff --git a/crates/carddav/src/addressbook/methods/get.rs b/crates/carddav/src/addressbook/methods/get.rs index 3159243..8bd4d6e 100644 --- a/crates/carddav/src/addressbook/methods/get.rs +++ b/crates/carddav/src/addressbook/methods/get.rs @@ -9,7 +9,6 @@ use http::{HeaderValue, Method, StatusCode, header}; use percent_encoding::{CONTROLS, utf8_percent_encode}; use rustical_dav::privileges::UserPrivilege; use rustical_dav::resource::Resource; -use rustical_ical::AddressObject; use rustical_store::auth::Principal; use rustical_store::{AddressbookStore, SubscriptionStore}; use std::str::FromStr; @@ -40,7 +39,7 @@ pub async fn route_get( let objects = addr_store.get_objects(&principal, &addressbook_id).await?; let vcf = objects .iter() - .map(AddressObject::get_vcf) + .map(|(_id, obj)| obj.get_vcf()) .collect::>() .join("\r\n"); diff --git a/crates/carddav/src/addressbook/methods/import.rs b/crates/carddav/src/addressbook/methods/import.rs index 3f4bc2a..82709e9 100644 --- a/crates/carddav/src/addressbook/methods/import.rs +++ b/crates/carddav/src/addressbook/methods/import.rs @@ -40,8 +40,9 @@ pub async fn route_import( }); card = card_mut.build(&HashMap::new()).unwrap(); } - - objects.push(card.try_into().unwrap()); + // TODO: Make nicer + let uid = card.get_uid().unwrap(); + objects.push((uid.to_owned(), card.into())); } if objects.is_empty() { diff --git a/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs b/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs index 53fff13..03fb11b 100644 --- a/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs +++ b/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs @@ -29,7 +29,7 @@ pub async fn get_objects_addressbook_multiget( principal: &str, addressbook_id: &str, store: &AS, -) -> Result<(Vec, Vec), Error> { +) -> Result<(Vec<(String, AddressObject)>, Vec), Error> { let mut result = vec![]; let mut not_found = vec![]; @@ -43,7 +43,7 @@ pub async fn get_objects_addressbook_multiget( .get_object(principal, addressbook_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(err) => return Err(err.into()), } @@ -74,11 +74,12 @@ pub async fn handle_addressbook_multiget( .await?; let mut responses = Vec::new(); - for object in objects { - let path = format!("{}/{}.vcf", path, object.get_id()); + for (object_id, object) in objects { + let path = format!("{path}/{object_id}.vcf"); responses.push( AddressObjectResource { object, + object_id, principal: principal.to_owned(), } .propfind(&path, prop, None, puri, user)?, diff --git a/crates/carddav/src/addressbook/methods/report/addressbook_query/mod.rs b/crates/carddav/src/addressbook/methods/report/addressbook_query/mod.rs index 1e463fe..6302584 100644 --- a/crates/carddav/src/addressbook/methods/report/addressbook_query/mod.rs +++ b/crates/carddav/src/addressbook/methods/report/addressbook_query/mod.rs @@ -15,8 +15,8 @@ pub async fn get_objects_addressbook_query( principal: &str, addressbook_id: &str, store: &AS, -) -> Result, Error> { +) -> Result, Error> { let mut objects = store.get_objects(principal, addressbook_id).await?; - objects.retain(|object| addr_query.filter.matches(object)); + objects.retain(|(_id, object)| addr_query.filter.matches(object)); Ok(objects) } diff --git a/crates/carddav/src/addressbook/methods/report/addressbook_query/tests.rs b/crates/carddav/src/addressbook/methods/report/addressbook_query/tests.rs index 5c84224..f292562 100644 --- a/crates/carddav/src/addressbook/methods/report/addressbook_query/tests.rs +++ b/crates/carddav/src/addressbook/methods/report/addressbook_query/tests.rs @@ -64,7 +64,7 @@ const FILTER_2: &str = r#" #[case(VCF_2, FILTER_2, true)] fn test_filter(#[case] vcf: &str, #[case] filter: &str, #[case] matches: bool) { dbg!(vcf); - let obj = AddressObject::from_vcf(String::new(), vcf.to_owned()).unwrap(); + let obj = AddressObject::from_vcf(vcf.to_owned()).unwrap(); let filter = FilterElement::parse_str(filter).unwrap(); assert_eq!(matches, filter.matches(&obj)); } diff --git a/crates/carddav/src/addressbook/methods/report/mod.rs b/crates/carddav/src/addressbook/methods/report/mod.rs index dfba5e0..8191b1a 100644 --- a/crates/carddav/src/addressbook/methods/report/mod.rs +++ b/crates/carddav/src/addressbook/methods/report/mod.rs @@ -55,7 +55,7 @@ impl ReportRequest { } fn objects_response( - objects: Vec, + objects: Vec<(String, AddressObject)>, not_found: Vec, path: &str, principal: &str, @@ -64,11 +64,12 @@ fn objects_response( prop: &PropfindType, ) -> Result, Error> { let mut responses = Vec::new(); - for object in objects { - let path = format!("{}/{}.vcf", path, object.get_id()); + for (object_id, object) in objects { + let path = format!("{}/{}.vcf", path, &object_id); responses.push( AddressObjectResource { object, + object_id, principal: principal.to_owned(), } .propfind(&path, prop, None, puri, user)?, diff --git a/crates/carddav/src/addressbook/methods/report/sync_collection.rs b/crates/carddav/src/addressbook/methods/report/sync_collection.rs index f27837b..e1ac852 100644 --- a/crates/carddav/src/addressbook/methods/report/sync_collection.rs +++ b/crates/carddav/src/addressbook/methods/report/sync_collection.rs @@ -32,11 +32,12 @@ pub async fn handle_sync_collection( .await?; let mut responses = Vec::new(); - for object in new_objects { - let path = format!("{}/{}.vcf", path.trim_end_matches('/'), object.get_id()); + for (object_id, object) in new_objects { + let path = format!("{}/{}.vcf", path.trim_end_matches('/'), object_id); responses.push( AddressObjectResource { object, + object_id, principal: principal.to_owned(), } .propfind(&path, &sync_collection.prop, None, puri, user)?, diff --git a/crates/carddav/src/addressbook/service.rs b/crates/carddav/src/addressbook/service.rs index 4ae19d6..0e838f1 100644 --- a/crates/carddav/src/addressbook/service.rs +++ b/crates/carddav/src/addressbook/service.rs @@ -78,7 +78,8 @@ impl ResourceService .get_objects(principal, addressbook_id) .await? .into_iter() - .map(|object| AddressObjectResource { + .map(|(object_id, object)| AddressObjectResource { + object_id, object, principal: principal.to_owned(), }) @@ -91,7 +92,7 @@ impl ResourceService file: Self::Resource, ) -> Result<(), Self::Error> { self.addr_store - .update_addressbook(principal.to_owned(), addressbook_id.to_owned(), file.into()) + .update_addressbook(principal, addressbook_id, file.into()) .await?; Ok(()) } diff --git a/crates/ical/src/address_object.rs b/crates/ical/src/address_object.rs index 79c68ec..e631fec 100644 --- a/crates/ical/src/address_object.rs +++ b/crates/ical/src/address_object.rs @@ -9,30 +9,19 @@ use std::{collections::HashMap, io::BufReader}; #[derive(Debug, Clone)] pub struct AddressObject { - id: String, vcf: String, vcard: VcardContact, } -impl TryFrom for AddressObject { - type Error = Error; - - fn try_from(vcard: VcardContact) -> Result { - let uid = vcard - .get_uid() - .ok_or_else(|| Error::InvalidData("missing UID".to_owned()))? - .to_owned(); +impl From for AddressObject { + fn from(vcard: VcardContact) -> Self { let vcf = vcard.generate(); - Ok(Self { - vcf, - vcard, - id: uid, - }) + Self { vcf, vcard } } } impl AddressObject { - pub fn from_vcf(id: String, vcf: String) -> Result { + pub fn from_vcf(vcf: String) -> Result { let mut parser = vcard::VcardParser::new(BufReader::new(vcf.as_bytes())); let vcard = parser.next().ok_or(Error::MissingContact)??; if parser.next().is_some() { @@ -40,18 +29,12 @@ impl AddressObject { "multiple vcards, only one allowed".to_owned(), )); } - Ok(Self { id, vcf, vcard }) - } - - #[must_use] - pub fn get_id(&self) -> &str { - &self.id + Ok(Self { vcf, vcard }) } #[must_use] pub fn get_etag(&self) -> String { let mut hasher = Sha256::new(); - hasher.update(self.get_id()); hasher.update(self.get_vcf()); format!("\"{:x}\"", hasher.finalize()) } diff --git a/crates/store/src/addressbook_store.rs b/crates/store/src/addressbook_store.rs index 325071b..c5bf2d6 100644 --- a/crates/store/src/addressbook_store.rs +++ b/crates/store/src/addressbook_store.rs @@ -15,8 +15,8 @@ pub trait AddressbookStore: Send + Sync + 'static { async fn update_addressbook( &self, - principal: String, - id: String, + principal: &str, + id: &str, addressbook: Addressbook, ) -> Result<(), Error>; async fn insert_addressbook(&self, addressbook: Addressbook) -> Result<(), Error>; @@ -33,7 +33,7 @@ pub trait AddressbookStore: Send + Sync + 'static { principal: &str, addressbook_id: &str, synctoken: i64, - ) -> Result<(Vec, Vec, i64), Error>; + ) -> Result<(Vec<(String, AddressObject)>, Vec, i64), Error>; async fn addressbook_metadata( &self, @@ -45,7 +45,7 @@ pub trait AddressbookStore: Send + Sync + 'static { &self, principal: &str, addressbook_id: &str, - ) -> Result, Error>; + ) -> Result, Error>; async fn get_object( &self, principal: &str, @@ -55,8 +55,9 @@ pub trait AddressbookStore: Send + Sync + 'static { ) -> Result; async fn put_object( &self, - principal: String, - addressbook_id: String, + principal: &str, + addressbook_id: &str, + object_id: &str, object: AddressObject, overwrite: bool, ) -> Result<(), Error>; @@ -77,7 +78,7 @@ pub trait AddressbookStore: Send + Sync + 'static { async fn import_addressbook( &self, addressbook: Addressbook, - objects: Vec, + objects: Vec<(String, AddressObject)>, merge_existing: bool, ) -> Result<(), Error>; } diff --git a/crates/store_sqlite/src/addressbook_store/birthday_calendar.rs b/crates/store_sqlite/src/addressbook_store/birthday_calendar.rs index 8a73824..a79d50b 100644 --- a/crates/store_sqlite/src/addressbook_store/birthday_calendar.rs +++ b/crates/store_sqlite/src/addressbook_store/birthday_calendar.rs @@ -8,7 +8,6 @@ use rustical_store::{ }; use sha2::{Digest, Sha256}; use sqlx::{Executor, Sqlite}; -use std::collections::HashMap; use tracing::instrument; pub const BIRTHDAYS_PREFIX: &str = "_birthdays_"; @@ -330,13 +329,14 @@ impl CalendarStore for SqliteAddressbookStore { .ok_or(Error::NotFound)?; let (objects, deleted_objects, new_synctoken) = AddressbookStore::sync_changes(self, principal, cal_id, synctoken).await?; - let objects: Result>, rustical_ical::Error> = objects - .iter() - .map(AddressObject::get_birthday_object) - .collect(); - let objects = objects?.into_iter().flatten().collect(); - - Ok((objects, deleted_objects, new_synctoken)) + todo!(); + // let objects: Result>, rustical_ical::Error> = objects + // .iter() + // .map(AddressObject::get_birthday_object) + // .collect(); + // let objects = objects?.into_iter().flatten().collect(); + // + // Ok((objects, deleted_objects, new_synctoken)) } #[instrument] @@ -357,21 +357,22 @@ impl CalendarStore for SqliteAddressbookStore { principal: &str, cal_id: &str, ) -> Result, Error> { - let cal_id = cal_id - .strip_prefix(BIRTHDAYS_PREFIX) - .ok_or(Error::NotFound)?; - let objects: Result>, rustical_ical::Error> = - AddressbookStore::get_objects(self, principal, cal_id) - .await? - .iter() - .map(AddressObject::get_significant_dates) - .collect(); - let objects = objects? - .into_iter() - .flat_map(HashMap::into_values) - .collect(); - - Ok(objects) + todo!() + // let cal_id = cal_id + // .strip_prefix(BIRTHDAYS_PREFIX) + // .ok_or(Error::NotFound)?; + // let objects: Result>, rustical_ical::Error> = + // AddressbookStore::get_objects(self, principal, cal_id) + // .await? + // .iter() + // .map(AddressObject::get_significant_dates) + // .collect(); + // let objects = objects? + // .into_iter() + // .flat_map(HashMap::into_values) + // .collect(); + // + // Ok(objects) } #[instrument] diff --git a/crates/store_sqlite/src/addressbook_store/mod.rs b/crates/store_sqlite/src/addressbook_store/mod.rs index b026c3d..196c241 100644 --- a/crates/store_sqlite/src/addressbook_store/mod.rs +++ b/crates/store_sqlite/src/addressbook_store/mod.rs @@ -19,11 +19,11 @@ struct AddressObjectRow { vcf: String, } -impl TryFrom for AddressObject { +impl TryFrom for (String, AddressObject) { type Error = rustical_store::Error; fn try_from(value: AddressObjectRow) -> Result { - Ok(Self::from_vcf(value.id, value.vcf)?) + Ok((value.id, AddressObject::from_vcf(value.vcf)?)) } } @@ -290,7 +290,7 @@ impl SqliteAddressbookStore { principal: &str, addressbook_id: &str, synctoken: i64, - ) -> Result<(Vec, Vec, i64), rustical_store::Error> { + ) -> Result<(Vec<(String, AddressObject)>, Vec, i64), rustical_store::Error> { struct Row { object_id: String, synctoken: i64, @@ -318,7 +318,7 @@ impl SqliteAddressbookStore { for Row { object_id, .. } in changes { match Self::_get_object(&mut *conn, principal, addressbook_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(err) => return Err(err), } @@ -353,7 +353,7 @@ impl SqliteAddressbookStore { executor: E, principal: &str, addressbook_id: &str, - ) -> Result, rustical_store::Error> { + ) -> Result, rustical_store::Error> { sqlx::query_as!( AddressObjectRow, "SELECT id, vcf FROM addressobjects WHERE principal = ? AND addressbook_id = ? AND deleted_at IS NULL", @@ -374,7 +374,7 @@ impl SqliteAddressbookStore { object_id: &str, show_deleted: bool, ) -> Result { - sqlx::query_as!( + let (id, object) = sqlx::query_as!( AddressObjectRow, "SELECT id, vcf FROM addressobjects WHERE (principal, addressbook_id, id) = (?, ?, ?) AND ((deleted_at IS NULL) OR ?)", principal, @@ -385,17 +385,20 @@ impl SqliteAddressbookStore { .fetch_one(executor) .await .map_err(crate::Error::from)? - .try_into() + .try_into()?; + assert_eq!(id, object_id); + Ok(object) } async fn _put_object<'e, E: Executor<'e, Database = Sqlite>>( executor: E, principal: &str, addressbook_id: &str, + object_id: &str, object: &AddressObject, overwrite: bool, ) -> Result<(), rustical_store::Error> { - let (object_id, vcf) = (object.get_id(), object.get_vcf()); + let vcf = object.get_vcf(); (if overwrite { sqlx::query!( @@ -500,10 +503,12 @@ impl AddressbookStore for SqliteAddressbookStore { #[instrument] async fn update_addressbook( &self, - principal: String, - id: String, + principal: &str, + id: &str, addressbook: Addressbook, ) -> Result<(), rustical_store::Error> { + assert_eq!(principal, &addressbook.principal); + assert_eq!(id, &addressbook.id); Self::_update_addressbook(&self.db, &principal, &id, &addressbook).await } @@ -569,7 +574,7 @@ impl AddressbookStore for SqliteAddressbookStore { principal: &str, addressbook_id: &str, synctoken: i64, - ) -> Result<(Vec, Vec, i64), rustical_store::Error> { + ) -> Result<(Vec<(String, AddressObject)>, Vec, i64), rustical_store::Error> { Self::_sync_changes(&self.db, principal, addressbook_id, synctoken).await } @@ -601,7 +606,7 @@ impl AddressbookStore for SqliteAddressbookStore { &self, principal: &str, addressbook_id: &str, - ) -> Result, rustical_store::Error> { + ) -> Result, rustical_store::Error> { Self::_get_objects(&self.db, principal, addressbook_id).await } @@ -619,8 +624,9 @@ impl AddressbookStore for SqliteAddressbookStore { #[instrument] async fn put_object( &self, - principal: String, - addressbook_id: String, + principal: &str, + addressbook_id: &str, + object_id: &str, object: AddressObject, overwrite: bool, ) -> Result<(), rustical_store::Error> { @@ -630,9 +636,15 @@ impl AddressbookStore for SqliteAddressbookStore { .await .map_err(crate::Error::from)?; - let object_id = object.get_id().to_owned(); - - Self::_put_object(&mut *tx, &principal, &addressbook_id, &object, overwrite).await?; + Self::_put_object( + &mut *tx, + principal, + addressbook_id, + object_id, + &object, + overwrite, + ) + .await?; let sync_token = Self::log_object_operation( &mut tx, @@ -648,7 +660,7 @@ impl AddressbookStore for SqliteAddressbookStore { self.send_push_notification( CollectionOperationInfo::Content { sync_token }, - self.get_addressbook(&principal, &addressbook_id, false) + self.get_addressbook(principal, addressbook_id, false) .await? .push_topic, ); @@ -733,7 +745,7 @@ impl AddressbookStore for SqliteAddressbookStore { async fn import_addressbook( &self, addressbook: Addressbook, - objects: Vec, + objects: Vec<(String, AddressObject)>, merge_existing: bool, ) -> Result<(), Error> { let mut tx = self @@ -758,11 +770,12 @@ impl AddressbookStore for SqliteAddressbookStore { } let mut sync_token = None; - for object in objects { + for (object_id, object) in objects { Self::_put_object( &mut *tx, &addressbook.principal, &addressbook.id, + &object_id, &object, false, ) @@ -773,7 +786,7 @@ impl AddressbookStore for SqliteAddressbookStore { &mut tx, &addressbook.principal, &addressbook.id, - object.get_id(), + &object_id, ChangeOperation::Add, ) .await?,