CardDAV GET + PUT
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user