CardDAV GET + PUT
This commit is contained in:
@@ -15,6 +15,7 @@ pub struct DavConfig {
|
||||
pub max_locks_per_user: usize,
|
||||
pub max_changes: usize,
|
||||
pub max_match_results: usize,
|
||||
pub max_vcard_size: usize,
|
||||
}
|
||||
|
||||
impl DavConfig {
|
||||
@@ -37,6 +38,9 @@ impl DavConfig {
|
||||
max_match_results: config
|
||||
.property("dav.limits.max-match-results")
|
||||
.unwrap_or(1000),
|
||||
max_vcard_size: config
|
||||
.property("dav.limits.size.vcard")
|
||||
.unwrap_or(512 * 1024),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +258,7 @@ pub struct DavResourceId {
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DavResources {
|
||||
pub files: IdBimap<DavResource>,
|
||||
pub paths: IdBimap<DavResource>,
|
||||
pub size: u64,
|
||||
pub modseq: Option<u64>,
|
||||
}
|
||||
@@ -508,7 +508,7 @@ pub fn ip_to_bytes_prefix(prefix: u8, ip: &IpAddr) -> Vec<u8> {
|
||||
impl DavResources {
|
||||
pub fn subtree(&self, search_path: &str) -> impl Iterator<Item = &DavResource> {
|
||||
let prefix = format!("{search_path}/");
|
||||
self.files
|
||||
self.paths
|
||||
.iter()
|
||||
.filter(move |item| item.name.starts_with(&prefix) || item.name == search_path)
|
||||
}
|
||||
@@ -519,7 +519,7 @@ impl DavResources {
|
||||
depth: usize,
|
||||
) -> impl Iterator<Item = &DavResource> {
|
||||
let prefix = format!("{search_path}/");
|
||||
self.files.iter().filter(move |item| {
|
||||
self.paths.iter().filter(move |item| {
|
||||
item.name
|
||||
.strip_prefix(&prefix)
|
||||
.is_some_and(|name| name.as_bytes().iter().filter(|&&c| c == b'/').count() <= depth)
|
||||
@@ -528,14 +528,14 @@ impl DavResources {
|
||||
}
|
||||
|
||||
pub fn tree_with_depth(&self, depth: usize) -> impl Iterator<Item = &DavResource> {
|
||||
self.files.iter().filter(move |item| {
|
||||
self.paths.iter().filter(move |item| {
|
||||
item.name.as_bytes().iter().filter(|&&c| c == b'/').count() <= depth
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_ancestor_of(&self, ancestor: u32, descendant: u32) -> bool {
|
||||
let ancestor = &self.files.by_id(ancestor).unwrap().name;
|
||||
let descendant = &self.files.by_id(descendant).unwrap().name;
|
||||
let ancestor = &self.paths.by_id(ancestor).unwrap().name;
|
||||
let descendant = &self.paths.by_id(descendant).unwrap().name;
|
||||
|
||||
let prefix = format!("{ancestor}/");
|
||||
descendant.starts_with(&prefix) || descendant == ancestor
|
||||
|
||||
@@ -71,6 +71,7 @@ impl Server {
|
||||
if shared_containers.is_empty() {
|
||||
return Ok(shared_containers);
|
||||
}
|
||||
let todo = "maybe cache?";
|
||||
let mut shared_items = RoaringBitmap::new();
|
||||
for document_id in shared_containers {
|
||||
if let Some(documents_in_folder) = self
|
||||
@@ -147,9 +148,9 @@ impl Server {
|
||||
) -> trc::Result<bool> {
|
||||
let to_collection = to_collection.into();
|
||||
let check_acls = check_acls.into();
|
||||
for &grant_account_id in [access_token.primary_id]
|
||||
.iter()
|
||||
.chain(access_token.member_of.clone().iter())
|
||||
for grant_account_id in [access_token.primary_id]
|
||||
.into_iter()
|
||||
.chain(access_token.member_of.iter().copied())
|
||||
{
|
||||
match self
|
||||
.core
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
|
||||
use ahash::AHashSet;
|
||||
use jmap_proto::types::{property::Property, value::AclGrant};
|
||||
use rkyv::{
|
||||
option::ArchivedOption,
|
||||
primitive::{ArchivedU32, ArchivedU64},
|
||||
string::ArchivedString,
|
||||
};
|
||||
use std::{borrow::Cow, fmt::Debug};
|
||||
use store::{
|
||||
Serialize, SerializeInfallible, SerializedVersion,
|
||||
@@ -20,21 +25,13 @@ use crate::auth::AsTenantId;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum IndexValue<'x> {
|
||||
Text {
|
||||
Index {
|
||||
field: u8,
|
||||
value: Cow<'x, str>,
|
||||
value: IndexItem<'x>,
|
||||
},
|
||||
U32 {
|
||||
IndexList {
|
||||
field: u8,
|
||||
value: Option<u32>,
|
||||
},
|
||||
U64 {
|
||||
field: u8,
|
||||
value: Option<u64>,
|
||||
},
|
||||
U32List {
|
||||
field: u8,
|
||||
value: Cow<'x, [u32]>,
|
||||
value: Vec<IndexItem<'x>>,
|
||||
},
|
||||
Tag {
|
||||
field: u8,
|
||||
@@ -51,6 +48,156 @@ pub enum IndexValue<'x> {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IndexItem<'x> {
|
||||
Vec(Vec<u8>),
|
||||
Slice(&'x [u8]),
|
||||
ShortInt([u8; std::mem::size_of::<u32>()]),
|
||||
LongInt([u8; std::mem::size_of::<u64>()]),
|
||||
None,
|
||||
}
|
||||
|
||||
impl IndexItem<'_> {
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
match self {
|
||||
IndexItem::Vec(v) => v,
|
||||
IndexItem::Slice(s) => s,
|
||||
IndexItem::ShortInt(s) => s,
|
||||
IndexItem::LongInt(s) => s,
|
||||
IndexItem::None => &[],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_owned(self) -> Vec<u8> {
|
||||
match self {
|
||||
IndexItem::Vec(v) => v,
|
||||
IndexItem::Slice(s) => s.to_vec(),
|
||||
IndexItem::ShortInt(s) => s.to_vec(),
|
||||
IndexItem::LongInt(s) => s.to_vec(),
|
||||
IndexItem::None => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
IndexItem::Vec(v) => v.is_empty(),
|
||||
IndexItem::Slice(s) => s.is_empty(),
|
||||
IndexItem::None => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for IndexItem<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.as_slice() == other.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for IndexItem<'_> {}
|
||||
|
||||
impl std::hash::Hash for IndexItem<'_> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
match self {
|
||||
IndexItem::Vec(v) => v.as_slice().hash(state),
|
||||
IndexItem::Slice(s) => s.hash(state),
|
||||
IndexItem::ShortInt(s) => s.as_slice().hash(state),
|
||||
IndexItem::LongInt(s) => s.as_slice().hash(state),
|
||||
IndexItem::None => 0.hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for IndexItem<'_> {
|
||||
fn from(value: u32) -> Self {
|
||||
IndexItem::ShortInt(value.to_be_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&u32> for IndexItem<'_> {
|
||||
fn from(value: &u32) -> Self {
|
||||
IndexItem::ShortInt(value.to_be_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for IndexItem<'_> {
|
||||
fn from(value: u64) -> Self {
|
||||
IndexItem::LongInt(value.to_be_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<&'x [u8]> for IndexItem<'x> {
|
||||
fn from(value: &'x [u8]) -> Self {
|
||||
IndexItem::Slice(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for IndexItem<'_> {
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
IndexItem::Vec(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<&'x str> for IndexItem<'x> {
|
||||
fn from(value: &'x str) -> Self {
|
||||
IndexItem::Slice(value.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<&'x String> for IndexItem<'x> {
|
||||
fn from(value: &'x String) -> Self {
|
||||
IndexItem::Slice(value.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for IndexItem<'_> {
|
||||
fn from(value: String) -> Self {
|
||||
IndexItem::Vec(value.into_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<&'x ArchivedString> for IndexItem<'x> {
|
||||
fn from(value: &'x ArchivedString) -> Self {
|
||||
IndexItem::Slice(value.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArchivedU32> for IndexItem<'_> {
|
||||
fn from(value: ArchivedU32) -> Self {
|
||||
IndexItem::ShortInt(value.to_native().to_be_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ArchivedU32> for IndexItem<'_> {
|
||||
fn from(value: &ArchivedU32) -> Self {
|
||||
IndexItem::ShortInt(value.to_native().to_be_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArchivedU64> for IndexItem<'_> {
|
||||
fn from(value: ArchivedU64) -> Self {
|
||||
IndexItem::LongInt(value.to_native().to_be_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x, T: Into<IndexItem<'x>>> From<Option<T>> for IndexItem<'x> {
|
||||
fn from(value: Option<T>) -> Self {
|
||||
match value {
|
||||
Some(v) => v.into(),
|
||||
None => IndexItem::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x, T: Into<IndexItem<'x>>> From<ArchivedOption<T>> for IndexItem<'x> {
|
||||
fn from(value: ArchivedOption<T>) -> Self {
|
||||
match value {
|
||||
ArchivedOption::Some(v) => v.into(),
|
||||
ArchivedOption::None => IndexItem::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IndexableObject: Sync + Send {
|
||||
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>>;
|
||||
}
|
||||
@@ -164,38 +311,20 @@ impl<C: IndexableObject, N: IndexableAndSerializableObject> IntoOperations
|
||||
|
||||
fn build_index(batch: &mut BatchBuilder, item: IndexValue<'_>, tenant_id: Option<u32>, set: bool) {
|
||||
match item {
|
||||
IndexValue::Text { field, value } => {
|
||||
IndexValue::Index { field, value } => {
|
||||
if !value.is_empty() {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.into_owned().into_bytes(),
|
||||
key: value.into_owned(),
|
||||
set,
|
||||
});
|
||||
}
|
||||
}
|
||||
IndexValue::U32 { field, value } => {
|
||||
if let Some(value) = value {
|
||||
IndexValue::IndexList { field, value } => {
|
||||
for key in value {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set,
|
||||
});
|
||||
}
|
||||
}
|
||||
IndexValue::U64 { field, value } => {
|
||||
if let Some(value) = value {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set,
|
||||
});
|
||||
}
|
||||
}
|
||||
IndexValue::U32List { field, value } => {
|
||||
for item in value.as_ref() {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: (*item).serialize(),
|
||||
key: key.into_owned(),
|
||||
set,
|
||||
});
|
||||
}
|
||||
@@ -249,18 +378,18 @@ fn merge_index(
|
||||
) -> trc::Result<()> {
|
||||
match (current, change) {
|
||||
(
|
||||
IndexValue::Text {
|
||||
IndexValue::Index {
|
||||
field,
|
||||
value: old_value,
|
||||
},
|
||||
IndexValue::Text {
|
||||
IndexValue::Index {
|
||||
value: new_value, ..
|
||||
},
|
||||
) => {
|
||||
if !old_value.is_empty() {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: old_value.into_owned().into_bytes(),
|
||||
key: old_value.into_owned(),
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
@@ -268,89 +397,39 @@ fn merge_index(
|
||||
if !new_value.is_empty() {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: new_value.into_owned().into_bytes(),
|
||||
key: new_value.into_owned(),
|
||||
set: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
(
|
||||
IndexValue::U32 {
|
||||
IndexValue::IndexList {
|
||||
field,
|
||||
value: old_value,
|
||||
},
|
||||
IndexValue::U32 {
|
||||
IndexValue::IndexList {
|
||||
value: new_value, ..
|
||||
},
|
||||
) => {
|
||||
if let Some(value) = old_value {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
if let Some(value) = new_value {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
(
|
||||
IndexValue::U64 {
|
||||
field,
|
||||
value: old_value,
|
||||
},
|
||||
IndexValue::U64 {
|
||||
value: new_value, ..
|
||||
},
|
||||
) => {
|
||||
if let Some(value) = old_value {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
if let Some(value) = new_value {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
(
|
||||
IndexValue::U32List {
|
||||
field,
|
||||
value: old_value,
|
||||
},
|
||||
IndexValue::U32List {
|
||||
value: new_value, ..
|
||||
},
|
||||
) => {
|
||||
let mut add_values = AHashSet::new();
|
||||
let mut remove_values = AHashSet::new();
|
||||
let mut remove_values = AHashSet::from_iter(old_value);
|
||||
|
||||
for current_value in old_value.as_ref() {
|
||||
remove_values.insert(current_value);
|
||||
}
|
||||
for value in new_value.as_ref() {
|
||||
for value in new_value {
|
||||
if !remove_values.remove(&value) {
|
||||
add_values.insert(value);
|
||||
}
|
||||
}
|
||||
|
||||
for (values, set) in [(add_values, true), (remove_values, false)] {
|
||||
for value in values {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set,
|
||||
key: value.into_owned(),
|
||||
set: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for value in remove_values {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.into_owned(),
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
(
|
||||
IndexValue::Tag {
|
||||
|
||||
Reference in New Issue
Block a user