CardDAV GET + PUT

This commit is contained in:
mdecimus
2025-03-29 19:46:05 +01:00
parent 372f2bec70
commit 76f085ab7c
36 changed files with 1203 additions and 501 deletions

View File

@@ -9,6 +9,8 @@ use jmap_proto::types::{acl::Acl, value::AclGrant};
use store::{SERIALIZE_OBJ_14_V1, SerializedVersion};
use utils::map::vec_map::VecMap;
use crate::DavName;
#[derive(
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
)]
@@ -38,9 +40,8 @@ pub struct CalendarPreferences {
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
)]
pub struct CalendarEvent {
pub name: Option<String>,
pub names: Vec<DavName>,
pub event: ICalendar,
pub calendar_ids: Vec<u32>,
pub user_properties: VecMap<u32, ICalendar>,
pub created: u64,
pub updated: u64,

View File

@@ -4,16 +4,23 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject};
use jmap_proto::types::{property::Property, value::AclGrant};
use common::storage::index::{
IndexItem, IndexValue, IndexableAndSerializableObject, IndexableObject,
};
use jmap_proto::types::value::AclGrant;
use store::SerializeInfallible;
use super::{AddressBook, ArchivedAddressBook, ContactCard};
use crate::{IDX_CARD_UID, IDX_NAME};
use super::{AddressBook, ArchivedAddressBook, ArchivedContactCard, ContactCard};
impl IndexableObject for AddressBook {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
// Note: When adding a new value with index id above 0u8, tune `build_hierarchy`` to skip
// this value during iteration.
[
IndexValue::Text {
field: Property::Name.into(),
IndexValue::Index {
field: IDX_NAME,
value: self.name.as_str().into(),
},
IndexValue::Acl {
@@ -33,8 +40,8 @@ impl IndexableObject for AddressBook {
impl IndexableObject for &ArchivedAddressBook {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
[
IndexValue::Text {
field: Property::Name.into(),
IndexValue::Index {
field: IDX_NAME,
value: self.name.as_str().into(),
},
IndexValue::Acl {
@@ -61,21 +68,53 @@ impl IndexableAndSerializableObject for AddressBook {}
impl IndexableObject for ContactCard {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
[
IndexValue::Text {
field: Property::Name.into(),
value: self.name.as_str().into(),
IndexValue::IndexList {
field: IDX_NAME,
value: self
.names
.iter()
.map(|v| IndexItem::Vec(v.serialize()))
.collect::<Vec<_>>(),
},
IndexValue::U32List {
field: Property::ParentId.into(),
value: self.addressbook_ids.as_slice().into(),
IndexValue::Index {
field: IDX_CARD_UID,
value: self.card.uid().into(),
},
IndexValue::Quota {
used: self.dead_properties.size() as u32
+ self.display_name.as_ref().map_or(0, |n| n.len() as u32)
+ self.name.len() as u32
+ self.names.iter().map(|n| n.name.len() as u32).sum::<u32>()
+ self.size,
},
]
.into_iter()
}
}
impl IndexableObject for &ArchivedContactCard {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
[
IndexValue::IndexList {
field: IDX_NAME,
value: self
.names
.iter()
.map(|v| IndexItem::Vec(v.serialize()))
.collect::<Vec<_>>(),
},
IndexValue::Index {
field: IDX_CARD_UID,
value: self.card.uid().into(),
},
IndexValue::Quota {
used: self.dead_properties.size() as u32
+ self.display_name.as_ref().map_or(0, |n| n.len() as u32)
+ self.names.iter().map(|n| n.name.len() as u32).sum::<u32>()
+ self.size,
},
]
.into_iter()
}
}
impl IndexableAndSerializableObject for ContactCard {}

View File

@@ -9,7 +9,9 @@ pub mod index;
use calcard::vcard::VCard;
use dav_proto::schema::request::DeadProperty;
use jmap_proto::types::{acl::Acl, value::AclGrant};
use store::{SERIALIZE_OBJ_15_V1, SerializedVersion};
use store::{SERIALIZE_OBJ_15_V1, SERIALIZE_OBJ_16_V1, SerializedVersion};
use crate::DavName;
#[derive(
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
@@ -24,6 +26,8 @@ pub struct AddressBook {
pub subscribers: Vec<u32>,
pub dead_properties: DeadProperty,
pub acls: Vec<AclGrant>,
pub created: i64,
pub modified: i64,
}
pub enum AddressBookRight {
@@ -38,13 +42,12 @@ pub enum AddressBookRight {
)]
#[rkyv(derive(Debug))]
pub struct ContactCard {
pub name: String,
pub names: Vec<DavName>,
pub display_name: Option<String>,
pub addressbook_ids: Vec<u32>,
pub card: VCard,
pub dead_properties: DeadProperty,
pub created: u64,
pub updated: u64,
pub created: i64,
pub modified: i64,
pub size: u32,
}
@@ -78,3 +81,9 @@ impl SerializedVersion for AddressBook {
SERIALIZE_OBJ_15_V1
}
}
impl SerializedVersion for ContactCard {
fn serialize_version() -> u8 {
SERIALIZE_OBJ_16_V1
}
}

View File

@@ -21,7 +21,7 @@ impl IndexableObject for FileNode {
let mut values = Vec::with_capacity(6);
values.extend([
IndexValue::Text {
IndexValue::Index {
field: Property::Name.into(),
value: percent_encoding::percent_decode_str(&self.name)
.decode_utf8()
@@ -29,7 +29,7 @@ impl IndexableObject for FileNode {
.to_lowercase()
.into(),
},
IndexValue::U32 {
IndexValue::Index {
field: Property::ParentId.into(),
value: self.parent_id.into(),
},
@@ -44,7 +44,7 @@ impl IndexableObject for FileNode {
IndexValue::Blob {
value: file.blob_hash.clone(),
},
IndexValue::U32 {
IndexValue::Index {
field: Property::Size.into(),
value: size.into(),
},
@@ -63,11 +63,11 @@ impl IndexableObject for &ArchivedFileNode {
let mut values = Vec::with_capacity(6);
values.extend([
IndexValue::Text {
IndexValue::Index {
field: Property::Name.into(),
value: self.name.to_lowercase().into(),
},
IndexValue::U32 {
IndexValue::Index {
field: Property::ParentId.into(),
value: u32::from(self.parent_id).into(),
},
@@ -87,7 +87,7 @@ impl IndexableObject for &ArchivedFileNode {
IndexValue::Blob {
value: (&file.blob_hash).into(),
},
IndexValue::U32 {
IndexValue::Index {
field: Property::Size.into(),
value: size.into(),
},

View File

@@ -7,7 +7,7 @@
use std::sync::Arc;
use common::{DavResource, DavResourceId, DavResources, Server};
use jmap_proto::types::{collection::Collection, property::Property};
use jmap_proto::types::collection::Collection;
use store::{
Deserialize, IndexKey, IterateParams, SerializeInfallible, U32_LEN, ahash::AHashMap,
write::key::DeserializeBigEndian,
@@ -15,10 +15,10 @@ use store::{
use trc::AddContext;
use utils::bimap::IdBimap;
use crate::file::FileNode;
use crate::{DavName, IDX_NAME, file::FileNode};
pub trait DavHierarchy: Sync + Send {
fn fetch_dav_hierarchy(
fn fetch_dav_resources(
&self,
account_id: u32,
collection: Collection,
@@ -26,7 +26,7 @@ pub trait DavHierarchy: Sync + Send {
}
impl DavHierarchy for Server {
async fn fetch_dav_hierarchy(
async fn fetch_dav_resources(
&self,
account_id: u32,
collection: Collection,
@@ -65,20 +65,14 @@ impl DavHierarchy for Server {
}
}
#[derive(Default)]
struct DavTempResource {
name: String,
parent_id: Vec<u32>,
}
async fn build_hierarchy(
server: &Server,
account_id: u32,
collection: Collection,
) -> trc::Result<DavResources> {
let collection = u8::from(collection);
let mut containers: AHashMap<u32, DavTempResource> = AHashMap::with_capacity(16);
let mut resources: AHashMap<u32, DavTempResource> = AHashMap::with_capacity(16);
let mut containers: AHashMap<u32, String> = AHashMap::with_capacity(16);
let mut resources: AHashMap<u32, Vec<DavName>> = AHashMap::with_capacity(16);
server
.store()
@@ -88,14 +82,14 @@ async fn build_hierarchy(
account_id,
collection,
document_id: 0,
field: 0,
field: IDX_NAME,
key: 0u32.serialize(),
},
IndexKey {
account_id,
collection: collection + 1,
document_id: u32::MAX,
field: u8::MAX,
field: IDX_NAME,
key: u32::MAX.serialize(),
},
)
@@ -110,24 +104,17 @@ async fn build_hierarchy(
.get(U32_LEN)
.copied()
.ok_or_else(|| trc::Error::corrupted_key(key, None, trc::location!()))?;
let key_property = key
.get(U32_LEN + 1)
.copied()
.ok_or_else(|| trc::Error::corrupted_key(key, None, trc::location!()))?;
let resource = if key_collection == collection {
containers.entry(document_id).or_default()
if key_collection == collection {
containers.insert(
document_id,
std::str::from_utf8(value)
.map_err(|_| trc::Error::corrupted_key(key, None, trc::location!()))?
.to_string(),
);
} else {
resources.entry(document_id).or_default()
};
if key_property == u8::from(Property::Value) {
resource.name = std::str::from_utf8(value)
.map_err(|_| trc::Error::corrupted_key(key, None, trc::location!()))?
.to_string();
} else if key_property == u8::from(Property::ParentId) {
resource.parent_id.push(
u32::deserialize(value)
resources.entry(document_id).or_default().push(
DavName::deserialize(value)
.map_err(|_| trc::Error::corrupted_key(key, None, trc::location!()))?,
);
}
@@ -139,21 +126,21 @@ async fn build_hierarchy(
.caused_by(trc::location!())?;
let mut files = DavResources {
files: IdBimap::with_capacity(containers.len() + resources.len()),
paths: IdBimap::with_capacity(containers.len() + resources.len()),
size: std::mem::size_of::<DavResources>() as u64,
modseq: None,
};
for (document_id, resource) in resources {
for parent_id in resource.parent_id {
if let Some(container) = containers.get(&parent_id) {
let name = format!("{}/{}", container.name, resource.name);
for (document_id, dav_names) in resources {
for dav_name in dav_names {
if let Some(container) = containers.get(&dav_name.parent_id) {
let name = format!("{}/{}", container, dav_name.name);
files.size += (std::mem::size_of::<u32>()
+ std::mem::size_of::<String>()
+ name.len()) as u64;
files.files.insert(DavResource {
files.paths.insert(DavResource {
document_id,
parent_id: parent_id.into(),
parent_id: dav_name.parent_id.into(),
name,
size: 0,
is_container: false,
@@ -163,14 +150,13 @@ async fn build_hierarchy(
}
}
for (document_id, container) in containers {
files.size += (std::mem::size_of::<u32>()
+ std::mem::size_of::<String>()
+ container.name.len()) as u64;
files.files.insert(DavResource {
for (document_id, name) in containers {
files.size +=
(std::mem::size_of::<u32>() + std::mem::size_of::<String>() + name.len()) as u64;
files.paths.insert(DavResource {
document_id,
parent_id: None,
name: container.name,
name,
size: 0,
is_container: true,
hierarchy_sequence: 0,
@@ -186,7 +172,7 @@ async fn build_file_hierarchy(server: &Server, account_id: u32) -> trc::Result<D
.await
.caused_by(trc::location!())?;
let mut files = DavResources {
files: IdBimap::with_capacity(list.len()),
paths: IdBimap::with_capacity(list.len()),
size: std::mem::size_of::<DavResources>() as u64,
modseq: None,
};
@@ -195,7 +181,7 @@ async fn build_file_hierarchy(server: &Server, account_id: u32) -> trc::Result<D
files.size += (std::mem::size_of::<u32>()
+ std::mem::size_of::<String>()
+ expanded.name.len()) as u64;
files.files.insert(DavResource {
files.paths.insert(DavResource {
document_id: expanded.document_id,
parent_id: expanded.parent_id,
name: expanded.name,

View File

@@ -4,7 +4,68 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use store::{Deserialize, SerializeInfallible, write::key::KeySerializer};
use utils::codec::leb128::Leb128Reader;
pub mod calendar;
pub mod contact;
pub mod file;
pub mod hierarchy;
pub const IDX_NAME: u8 = 0;
pub const IDX_CARD_UID: u8 = 1;
#[derive(
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
)]
#[rkyv(derive(Debug))]
pub struct DavName {
pub name: String,
pub parent_id: u32,
}
impl SerializeInfallible for DavName {
fn serialize(&self) -> Vec<u8> {
KeySerializer::new(self.name.len() + std::mem::size_of::<u32>())
.write_leb128(self.parent_id)
.write(self.name.as_bytes())
.finalize()
}
}
impl SerializeInfallible for ArchivedDavName {
fn serialize(&self) -> Vec<u8> {
KeySerializer::new(self.name.len() + std::mem::size_of::<u32>())
.write_leb128(self.parent_id.to_native())
.write(self.name.as_bytes())
.finalize()
}
}
impl DavName {
pub fn new(name: String, parent_id: u32) -> Self {
Self { name, parent_id }
}
}
impl Deserialize for DavName {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
let (parent_id, bytes_read) = bytes.read_leb128::<u32>().ok_or_else(|| {
trc::StoreEvent::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Value, bytes)
})?;
let name = bytes
.get(bytes_read..)
.and_then(|bytes| std::str::from_utf8(bytes).ok())
.ok_or_else(|| {
trc::StoreEvent::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Value, bytes)
})?
.to_string();
Ok(DavName { name, parent_id })
}
}