DAV file management delete

This commit is contained in:
mdecimus
2025-03-07 19:06:06 +01:00
parent eadd36f4cb
commit 110ec14fe6
40 changed files with 1162 additions and 416 deletions

View File

@@ -87,6 +87,7 @@ pub struct DefaultFolder {
#[derive(
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Clone, Copy, PartialEq, Eq, Hash, Debug,
)]
#[rkyv(derive(Debug))]
pub enum SpecialUse {
Inbox,
Trash,

View File

@@ -9,6 +9,7 @@ use std::{sync::Arc, time::Duration};
use directory::{Directory, QueryBy, Type, backend::internal::manage::ManageDirectory};
use jmap_proto::types::{
blob::BlobId, collection::Collection, property::Property, state::StateChange,
type_state::DataType,
};
use sieve::Sieve;
use store::{
@@ -379,16 +380,15 @@ impl Server {
})
}
pub async fn get_properties<U, I, P>(
pub async fn get_properties<U, I>(
&self,
account_id: u32,
collection: Collection,
iterate: &I,
property: P,
property: Property,
) -> trc::Result<Vec<(u32, U)>>
where
I: DocumentSet + Send + Sync,
P: AsRef<Property> + Sync + Send,
U: Deserialize + 'static,
{
let property: u8 = property.as_ref().into();
@@ -604,6 +604,17 @@ impl Server {
}
}
#[inline]
pub async fn broadcast_single_state_change(
&self,
account_id: u32,
change_id: u64,
data_type: DataType,
) {
self.broadcast_state_change(StateChange::new(account_id).with_change(data_type, change_id))
.await;
}
#[allow(clippy::blocks_in_conditions)]
pub async fn put_blob(
&self,

View File

@@ -45,7 +45,7 @@ use rustls::sign::CertifiedKey;
use tokio::sync::{Notify, Semaphore, mpsc};
use tokio_rustls::TlsConnector;
use utils::{
bimap::IdBimap,
bimap::{IdBimap, IdBimapItem},
cache::{Cache, CacheItemWeight, CacheWithTtl},
snowflake::SnowflakeIdGenerator,
};
@@ -250,11 +250,19 @@ pub struct NameWrapper(pub String);
#[derive(Debug, Default)]
pub struct Files {
pub files: IdBimap,
pub files: IdBimap<FileItem>,
pub size: u64,
pub modseq: Option<u64>,
}
#[derive(Debug, Default)]
pub struct FileItem {
pub document_id: u32,
pub parent_id: Option<u32>,
pub name: String,
pub is_container: bool,
}
#[derive(Clone, Default)]
pub struct Core {
pub storage: Storage,
@@ -480,3 +488,30 @@ pub fn ip_to_bytes_prefix(prefix: u8, ip: &IpAddr) -> Vec<u8> {
}
}
}
impl Files {
pub fn subtree(&self, search_path: &str) -> impl Iterator<Item = &FileItem> {
let prefix = format!("{search_path}/");
self.files
.iter()
.filter(move |item| item.name.starts_with(&prefix) || item.name == search_path)
}
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 prefix = format!("{ancestor}/");
descendant.starts_with(&prefix) || descendant == ancestor
}
}
impl IdBimapItem for FileItem {
fn id(&self) -> &u32 {
&self.document_id
}
fn name(&self) -> &str {
&self.name
}
}

View File

@@ -16,13 +16,22 @@ use utils::topological::{TopologicalSort, TopologicalSortIterator};
use crate::Server;
pub struct ExpandedFolders {
names: AHashMap<u32, (String, u32)>,
names: AHashMap<u32, (String, u32, bool)>,
iter: TopologicalSortIterator<u32>,
}
#[derive(Debug, Clone)]
pub struct ExpandedFolder {
pub name: String,
pub document_id: u32,
pub parent_id: Option<u32>,
pub is_container: bool,
}
pub trait FolderHierarchy: Sync + Send {
fn name(&self) -> String;
fn parent_id(&self) -> u32;
fn is_container(&self) -> bool;
}
pub trait TopologyBuilder: Sync + Send {
@@ -72,7 +81,10 @@ impl Server {
let parent_id = folder.parent_id();
topological_sort.insert(parent_id, document_id);
names.insert(document_id, (folder.name(), parent_id));
names.insert(
document_id,
(folder.name(), parent_id, folder.is_container()),
);
Ok(true)
},
@@ -154,7 +166,7 @@ impl ExpandedFolders {
where
T: Fn(u32, &str) -> Option<String>,
{
for (document_id, (name, _)) in &mut self.names {
for (document_id, (name, _, _)) in &mut self.names {
if let Some(new_name) = formatter(*document_id - 1, name) {
*name = new_name;
}
@@ -162,22 +174,36 @@ impl ExpandedFolders {
self
}
pub fn into_iterator(mut self) -> impl Iterator<Item = (u32, String)> + Sync + Send {
pub fn into_iterator(mut self) -> impl Iterator<Item = ExpandedFolder> + Sync + Send {
for folder_id in self.iter.by_ref() {
if folder_id != 0 {
if let Some((name, parent_name, parent_id)) =
self.names.get(&folder_id).and_then(|(name, parent_id)| {
self.names
.get(parent_id)
.map(|(parent_name, _)| (name, parent_name, *parent_id))
if let Some((name, parent_name, parent_id, is_container)) = self
.names
.get(&folder_id)
.and_then(|(name, parent_id, is_container)| {
self.names.get(parent_id).map(|(parent_name, _, _)| {
(name, parent_name, *parent_id, *is_container)
})
})
{
let name = format!("{parent_name}/{name}");
self.names.insert(folder_id, (name, parent_id));
self.names
.insert(folder_id, (name, parent_id, is_container));
}
}
}
self.names.into_iter().map(|(id, (name, _))| (id - 1, name))
self.names
.into_iter()
.map(|(id, (name, parent_id, is_container))| ExpandedFolder {
name,
document_id: id - 1,
is_container,
parent_id: if parent_id == 0 {
None
} else {
Some(parent_id - 1)
},
})
}
}

View File

@@ -10,7 +10,7 @@ use store::{
Serialize, SerializeInfallible,
write::{
Archiver, BatchBuilder, BitmapClass, BlobOp, DirectoryClass, IntoOperations, Operation,
ValueOp, assert::HashedValue,
assert::HashedValue,
},
};
use utils::BlobHash;
@@ -22,18 +22,19 @@ pub enum IndexValue<'x> {
Text { field: u8, value: Cow<'x, str> },
U32 { field: u8, value: Option<u32> },
U64 { field: u8, value: Option<u64> },
U32List { field: u8, value: &'x [u32] },
U32List { field: u8, value: Cow<'x, [u32]> },
Tag { field: u8, is_set: bool },
Blob { value: BlobHash },
Quota { used: u32 },
Acl { value: &'x [AclGrant] },
Acl { value: Cow<'x, [AclGrant]> },
}
pub trait IndexableObject:
Debug
+ Eq
+ Sync
+ Send
pub trait IndexableObject: Sync + Send {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>>;
}
pub trait IndexableAndSerializableObject:
IndexableObject
+ rkyv::Archive
+ for<'a> rkyv::Serialize<
rkyv::api::high::HighSerializer<
@@ -43,23 +44,22 @@ pub trait IndexableObject:
>,
>
{
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>>;
}
#[derive(Debug)]
pub struct ObjectIndexBuilder<T: IndexableObject> {
pub struct ObjectIndexBuilder<C: IndexableObject, N: IndexableAndSerializableObject> {
tenant_id: Option<u32>,
current: Option<HashedValue<T>>,
changes: Option<T>,
current: Option<HashedValue<C>>,
changes: Option<N>,
}
impl<T: IndexableObject> Default for ObjectIndexBuilder<T> {
impl<C: IndexableObject, N: IndexableAndSerializableObject> Default for ObjectIndexBuilder<C, N> {
fn default() -> Self {
Self::new()
}
}
impl<T: IndexableObject> ObjectIndexBuilder<T> {
impl<C: IndexableObject, N: IndexableAndSerializableObject> ObjectIndexBuilder<C, N> {
pub fn new() -> Self {
Self {
current: None,
@@ -68,30 +68,30 @@ impl<T: IndexableObject> ObjectIndexBuilder<T> {
}
}
pub fn with_current(mut self, current: HashedValue<T>) -> Self {
pub fn with_current(mut self, current: HashedValue<C>) -> Self {
self.current = Some(current);
self
}
pub fn with_changes(mut self, changes: T) -> Self {
pub fn with_changes(mut self, changes: N) -> Self {
self.changes = Some(changes);
self
}
pub fn with_current_opt(mut self, current: Option<HashedValue<T>>) -> Self {
pub fn with_current_opt(mut self, current: Option<HashedValue<C>>) -> Self {
self.current = current;
self
}
pub fn changes(&self) -> Option<&T> {
pub fn changes(&self) -> Option<&N> {
self.changes.as_ref()
}
pub fn changes_mut(&mut self) -> Option<&mut T> {
pub fn changes_mut(&mut self) -> Option<&mut N> {
self.changes.as_mut()
}
pub fn current(&self) -> Option<&HashedValue<T>> {
pub fn current(&self) -> Option<&HashedValue<C>> {
self.current.as_ref()
}
@@ -101,23 +101,35 @@ impl<T: IndexableObject> ObjectIndexBuilder<T> {
}
}
impl<T: IndexableObject> IntoOperations for ObjectIndexBuilder<T> {
impl<C: IndexableObject, N: IndexableAndSerializableObject> IntoOperations
for ObjectIndexBuilder<C, N>
{
fn build(self, batch: &mut BatchBuilder) -> trc::Result<()> {
match (self.current, self.changes) {
(None, Some(changes)) => {
// Insertion
build_batch(batch, &changes, self.tenant_id, true);
for item in changes.index_values() {
build_index(batch, item, self.tenant_id, true);
}
batch.set(Property::Value, Archiver::new(changes).serialize()?);
}
(Some(current), Some(changes)) => {
// Update
batch.assert_value(Property::Value, &current);
merge_batch(batch, current.inner, changes, self.tenant_id)?;
for (current, change) in current.inner.index_values().zip(changes.index_values()) {
if current != change {
merge_index(batch, current, change, self.tenant_id)?;
}
}
batch.set(Property::Value, Archiver::new(changes).serialize()?);
}
(Some(current), None) => {
// Deletion
batch.assert_value(Property::Value, &current);
build_batch(batch, &current.inner, self.tenant_id, false);
for item in current.inner.index_values() {
build_index(batch, item, self.tenant_id, false);
}
batch.clear(Property::Value);
}
(None, None) => unreachable!(),
@@ -127,316 +139,301 @@ impl<T: IndexableObject> IntoOperations for ObjectIndexBuilder<T> {
}
}
fn build_batch<T: IndexableObject>(
batch: &mut BatchBuilder,
object: &T,
tenant_id: Option<u32>,
set: bool,
) {
for item in object.index_values() {
match item {
IndexValue::Text { field, value } => {
if !value.is_empty() {
batch.ops.push(Operation::Index {
fn build_index(batch: &mut BatchBuilder, item: IndexValue<'_>, tenant_id: Option<u32>, set: bool) {
match item {
IndexValue::Text { field, value } => {
if !value.is_empty() {
batch.ops.push(Operation::Index {
field,
key: value.into_owned().into_bytes(),
set,
});
}
}
IndexValue::U32 { field, value } => {
if let Some(value) = 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(),
set,
});
}
}
IndexValue::Tag { field, is_set } => {
if is_set {
batch.ops.push(Operation::Bitmap {
class: BitmapClass::Tag {
field,
key: value.into_owned().into_bytes(),
set,
});
}
value: ().into(),
},
set,
});
}
IndexValue::U32 { field, value } => {
if let Some(value) = value {
batch.ops.push(Operation::Index {
field,
key: value.serialize(),
set,
});
}
}
IndexValue::Blob { value } => {
if set {
batch.set(BlobOp::Link { hash: value }, vec![]);
} else {
batch.clear(BlobOp::Link { hash: value });
}
IndexValue::U64 { field, value } => {
if let Some(value) = value {
batch.ops.push(Operation::Index {
field,
key: value.serialize(),
set,
});
}
}
IndexValue::Acl { value } => {
for item in value.as_ref() {
batch.ops.push(Operation::acl(
item.account_id,
if set {
item.grants.bitmap.serialize().into()
} else {
None
},
));
}
IndexValue::U32List { field, value } => {
for item in value {
batch.ops.push(Operation::Index {
field,
key: (*item).serialize(),
set,
});
}
}
IndexValue::Tag { field, is_set } => {
if is_set {
batch.ops.push(Operation::Bitmap {
class: BitmapClass::Tag {
field,
value: ().into(),
},
set,
});
}
}
IndexValue::Blob { value } => {
if set {
batch.set(BlobOp::Link { hash: value }, vec![]);
} else {
batch.clear(BlobOp::Link { hash: value });
}
}
IndexValue::Acl { value } => {
for item in value {
batch.ops.push(Operation::acl(
item.account_id,
if set {
item.grants.bitmap.serialize().into()
} else {
None
},
));
}
}
IndexValue::Quota { used } => {
let value = if set { used as i64 } else { -(used as i64) };
}
IndexValue::Quota { used } => {
let value = if set { used as i64 } else { -(used as i64) };
if let Some(account_id) = batch.last_account_id() {
batch.add(DirectoryClass::UsedQuota(account_id), value);
}
if let Some(account_id) = batch.last_account_id() {
batch.add(DirectoryClass::UsedQuota(account_id), value);
}
if let Some(tenant_id) = tenant_id {
batch.add(DirectoryClass::UsedQuota(tenant_id), value);
}
if let Some(tenant_id) = tenant_id {
batch.add(DirectoryClass::UsedQuota(tenant_id), value);
}
}
}
}
fn merge_batch<T: IndexableObject>(
fn merge_index(
batch: &mut BatchBuilder,
current: T,
changes: T,
current: IndexValue<'_>,
change: IndexValue<'_>,
tenant_id: Option<u32>,
) -> trc::Result<()> {
let mut has_changes = current != changes;
match (current, change) {
(
IndexValue::Text {
field,
value: old_value,
},
IndexValue::Text {
value: new_value, ..
},
) => {
if !old_value.is_empty() {
batch.ops.push(Operation::Index {
field,
key: old_value.into_owned().into_bytes(),
set: false,
});
}
for (current, change) in current.index_values().zip(changes.index_values()) {
if current == change {
continue;
if !new_value.is_empty() {
batch.ops.push(Operation::Index {
field,
key: new_value.into_owned().into_bytes(),
set: true,
});
}
}
has_changes = true;
match (current, change) {
(
IndexValue::Text {
(
IndexValue::U32 {
field,
value: old_value,
},
IndexValue::U32 {
value: new_value, ..
},
) => {
if let Some(value) = old_value {
batch.ops.push(Operation::Index {
field,
value: old_value,
},
IndexValue::Text {
value: new_value, ..
},
) => {
if !old_value.is_empty() {
batch.ops.push(Operation::Index {
field,
key: old_value.into_owned().into_bytes(),
set: false,
});
}
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 = HashSet::new();
let mut remove_values = HashSet::new();
if !new_value.is_empty() {
batch.ops.push(Operation::Index {
field,
key: new_value.into_owned().into_bytes(),
set: true,
});
for current_value in old_value.as_ref() {
remove_values.insert(current_value);
}
for value in new_value.as_ref() {
if !remove_values.remove(&value) {
add_values.insert(value);
}
}
(
IndexValue::U32 {
field,
value: old_value,
},
IndexValue::U32 {
value: new_value, ..
},
) => {
if let Some(value) = old_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: false,
set,
});
}
if let Some(value) = new_value {
batch.ops.push(Operation::Index {
}
}
(
IndexValue::Tag {
field,
is_set: was_set,
},
IndexValue::Tag { is_set, .. },
) => {
if was_set {
batch.ops.push(Operation::Bitmap {
class: BitmapClass::Tag {
field,
key: value.serialize(),
set: true,
});
}
value: ().into(),
},
set: false,
});
}
(
IndexValue::U64 {
field,
value: old_value,
},
IndexValue::U64 {
value: new_value, ..
},
) => {
if let Some(value) = old_value {
batch.ops.push(Operation::Index {
if is_set {
batch.ops.push(Operation::Bitmap {
class: BitmapClass::Tag {
field,
key: value.serialize(),
set: false,
});
}
if let Some(value) = new_value {
batch.ops.push(Operation::Index {
field,
key: value.serialize(),
set: true,
});
}
value: ().into(),
},
set: true,
});
}
(
IndexValue::U32List {
field,
value: old_value,
},
IndexValue::U32List {
value: new_value, ..
},
) => {
let mut add_values = HashSet::new();
let mut remove_values = HashSet::new();
for current_value in old_value {
remove_values.insert(current_value);
}
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,
});
}
}
}
(
IndexValue::Tag {
field,
is_set: was_set,
},
IndexValue::Tag { is_set, .. },
) => {
if was_set {
batch.ops.push(Operation::Bitmap {
class: BitmapClass::Tag {
field,
value: ().into(),
},
set: false,
});
}
if is_set {
batch.ops.push(Operation::Bitmap {
class: BitmapClass::Tag {
field,
value: ().into(),
},
set: true,
});
}
}
(IndexValue::Blob { value: old_hash }, IndexValue::Blob { value: new_hash }) => {
batch.clear(BlobOp::Link { hash: old_hash });
batch.set(BlobOp::Link { hash: new_hash }, vec![]);
}
(IndexValue::Acl { value: old_acl }, IndexValue::Acl { value: new_acl }) => {
match (!old_acl.is_empty(), !new_acl.is_empty()) {
(true, true) => {
// Remove deleted ACLs
for current_item in old_acl {
if !new_acl
.iter()
.any(|item| item.account_id == current_item.account_id)
{
batch
.ops
.push(Operation::acl(current_item.account_id, None));
}
}
(IndexValue::Blob { value: old_hash }, IndexValue::Blob { value: new_hash }) => {
batch.clear(BlobOp::Link { hash: old_hash });
batch.set(BlobOp::Link { hash: new_hash }, vec![]);
}
(IndexValue::Acl { value: old_acl }, IndexValue::Acl { value: new_acl }) => {
match (!old_acl.is_empty(), !new_acl.is_empty()) {
(true, true) => {
// Remove deleted ACLs
for current_item in old_acl.as_ref() {
if !new_acl
.iter()
.any(|item| item.account_id == current_item.account_id)
{
batch
.ops
.push(Operation::acl(current_item.account_id, None));
}
}
// Update ACLs
for item in new_acl {
let mut add_item = true;
for current_item in old_acl {
if item.account_id == current_item.account_id {
if item.grants == current_item.grants {
add_item = false;
}
break;
// Update ACLs
for item in new_acl.as_ref() {
let mut add_item = true;
for current_item in old_acl.as_ref() {
if item.account_id == current_item.account_id {
if item.grants == current_item.grants {
add_item = false;
}
}
if add_item {
batch.ops.push(Operation::acl(
item.account_id,
item.grants.bitmap.serialize().into(),
));
break;
}
}
}
(false, true) => {
// Add all ACLs
for item in new_acl {
if add_item {
batch.ops.push(Operation::acl(
item.account_id,
item.grants.bitmap.serialize().into(),
));
}
}
(true, false) => {
// Remove all ACLs
for item in old_acl {
batch.ops.push(Operation::acl(item.account_id, None));
}
}
(false, true) => {
// Add all ACLs
for item in new_acl.as_ref() {
batch.ops.push(Operation::acl(
item.account_id,
item.grants.bitmap.serialize().into(),
));
}
_ => {}
}
(true, false) => {
// Remove all ACLs
for item in old_acl.as_ref() {
batch.ops.push(Operation::acl(item.account_id, None));
}
}
_ => {}
}
(IndexValue::Quota { used: old_used }, IndexValue::Quota { used: new_used }) => {
let value = new_used as i64 - old_used as i64;
if let Some(account_id) = batch.last_account_id() {
batch.add(DirectoryClass::UsedQuota(account_id), value);
}
if let Some(tenant_id) = tenant_id {
batch.add(DirectoryClass::UsedQuota(tenant_id), value);
}
}
_ => unreachable!(),
}
}
(IndexValue::Quota { used: old_used }, IndexValue::Quota { used: new_used }) => {
let value = new_used as i64 - old_used as i64;
if let Some(account_id) = batch.last_account_id() {
batch.add(DirectoryClass::UsedQuota(account_id), value);
}
if has_changes {
batch.ops.push(Operation::Value {
class: Property::Value.into(),
op: ValueOp::Set(Archiver::new(changes).serialize()?.into()),
});
if let Some(tenant_id) = tenant_id {
batch.add(DirectoryClass::UsedQuota(tenant_id), value);
}
}
_ => unreachable!(),
}
Ok(())
}
impl IndexableObject for () {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
std::iter::empty()
}
}
impl IndexableAndSerializableObject for () {}