Propfind partial implementation

This commit is contained in:
mdecimus
2025-03-23 15:54:09 +01:00
parent 1a8efb2182
commit 2b8752a3fc
21 changed files with 801 additions and 473 deletions

View File

@@ -100,59 +100,64 @@ impl MailboxDestroy for Server {
// If the message is in multiple mailboxes, untag it from the current mailbox,
// otherwise delete it.
let mut destroy_ids = RoaringBitmap::new();
for (message_id, message_data_) in self
.get_properties::<Archive<AlignedBytes>, _>(
account_id,
Collection::Email,
&message_ids,
Property::Value,
)
.await?
{
// Remove mailbox from list
let prev_message_data = message_data_
.to_unarchived::<MessageData>()
.caused_by(trc::location!())?;
let mut batch = BatchBuilder::new();
if !prev_message_data
.inner
.mailboxes
.iter()
.any(|id| id.mailbox_id == document_id)
{
continue;
}
self.get_archives(
account_id,
Collection::Email,
&message_ids,
Property::Value,
|message_id, message_data_| {
// Remove mailbox from list
let prev_message_data = message_data_
.to_unarchived::<MessageData>()
.caused_by(trc::location!())?;
if !prev_message_data
.inner
.mailboxes
.iter()
.any(|id| id.mailbox_id == document_id)
{
return Ok(true);
}
if prev_message_data.inner.mailboxes.len() == 1 {
// Delete message
destroy_ids.insert(message_id);
continue;
}
if prev_message_data.inner.mailboxes.len() == 1 {
// Delete message
destroy_ids.insert(message_id);
return Ok(true);
}
let mut new_message_data = prev_message_data
.deserialize()
.caused_by(trc::location!())?;
let thread_id = new_message_data.thread_id;
let mut new_message_data = prev_message_data
.deserialize()
.caused_by(trc::location!())?;
let thread_id = new_message_data.thread_id;
new_message_data
.mailboxes
.retain(|id| id.mailbox_id != document_id);
new_message_data
.mailboxes
.retain(|id| id.mailbox_id != document_id);
// Untag message from mailbox
let mut batch = BatchBuilder::new();
batch
.with_account_id(account_id)
.with_collection(Collection::Email)
.update_document(message_id)
.custom(
ObjectIndexBuilder::new()
.with_changes(new_message_data)
.with_current(prev_message_data),
)
.caused_by(trc::location!())?;
// Untag message from mailbox
batch
.with_account_id(account_id)
.with_collection(Collection::Email)
.update_document(message_id)
.custom(
ObjectIndexBuilder::new()
.with_changes(new_message_data)
.with_current(prev_message_data),
)
.caused_by(trc::location!())?;
changes
.log_update(Collection::Email, Id::from_parts(thread_id, message_id));
Ok(true)
},
)
.await
.caused_by(trc::location!())?;
if !batch.is_empty() {
match self.core.storage.data.write(batch.build()).await {
Ok(_) => changes
.log_update(Collection::Email, Id::from_parts(thread_id, message_id)),
Ok(_) => {}
Err(err) if err.is_assertion_failure() => {
return Ok(Err(SetError::forbidden().with_description(concat!(
"Another process modified a message in this mailbox ",

View File

@@ -58,7 +58,7 @@ impl EmailDeletion for Server {
async fn emails_tombstone(
&self,
account_id: u32,
mut document_ids: RoaringBitmap,
document_ids: RoaringBitmap,
) -> trc::Result<(ChangeLogBuilder, RoaringBitmap)> {
// Create batch
let mut changes = ChangeLogBuilder::with_change_id(0);
@@ -68,69 +68,73 @@ impl EmailDeletion for Server {
batch
.with_account_id(account_id)
.with_collection(Collection::Email);
let mut batches = Vec::new();
let mut deleted_ids = RoaringBitmap::new();
self.get_archives(
account_id,
Collection::Email,
&document_ids,
Property::Value,
|document_id, data_| {
let data = data_
.to_unarchived::<MessageData>()
.caused_by(trc::location!())?;
let thread_id = u32::from(data.inner.thread_id);
for (document_id, data_) in self
.get_properties::<Archive<AlignedBytes>, _>(
account_id,
Collection::Email,
&document_ids,
Property::Value,
)
.await?
{
let data = data_
.to_unarchived::<MessageData>()
.caused_by(trc::location!())?;
let thread_id = u32::from(data.inner.thread_id);
// Log mailbox changes
for mailbox in data.inner.mailboxes.iter() {
changes.log_child_update(Collection::Mailbox, u32::from(mailbox.mailbox_id));
}
// Log mailbox changes
for mailbox in data.inner.mailboxes.iter() {
changes.log_child_update(Collection::Mailbox, u32::from(mailbox.mailbox_id));
}
// Log message deletion
changes.log_delete(Collection::Email, Id::from_parts(thread_id, document_id));
// Log message deletion
changes.log_delete(Collection::Email, Id::from_parts(thread_id, document_id));
// Log thread changes
changes.log_child_update(Collection::Thread, thread_id);
// Log thread changes
changes.log_child_update(Collection::Thread, thread_id);
// Add changes to batch
batch
.update_document(document_id)
.custom(ObjectIndexBuilder::<_, ()>::new().with_current(data))
.caused_by(trc::location!())?
.tag(
Property::MailboxIds,
TagValue::Id(MaybeDynamicId::Static(TOMBSTONE_ID)),
);
// Add changes to batch
batch
.update_document(document_id)
.custom(ObjectIndexBuilder::<_, ()>::new().with_current(data))
.caused_by(trc::location!())?
.tag(
Property::MailboxIds,
TagValue::Id(MaybeDynamicId::Static(TOMBSTONE_ID)),
);
deleted_ids.insert(document_id);
document_ids.remove(document_id);
if batch.ops.len() >= 1000 {
batches.push(std::mem::replace(&mut batch, BatchBuilder::new()));
batch
.with_account_id(account_id)
.with_collection(Collection::Email);
}
if batch.ops.len() >= 1000 {
Ok(true)
},
)
.await?;
for batch in batches.into_iter().chain([batch]) {
if !batch.is_empty() {
self.core
.storage
.data
.write(batch.build())
.await
.caused_by(trc::location!())?;
batch = BatchBuilder::new();
batch
.with_account_id(account_id)
.with_collection(Collection::Email);
}
}
if !batch.ops.is_empty() {
self.core
.storage
.data
.write(batch.build())
.await
.caused_by(trc::location!())?;
}
let not_destroyed = if document_ids.len() == deleted_ids.len() {
RoaringBitmap::new()
} else {
deleted_ids ^= document_ids;
deleted_ids
};
Ok((changes, document_ids))
Ok((changes, not_destroyed))
}
async fn purge_accounts(&self) {
@@ -240,19 +244,20 @@ impl EmailDeletion for Server {
// Find messages to destroy
let mut destroy_ids = RoaringBitmap::new();
for (document_id, data) in self
.get_properties::<Archive<AlignedBytes>, _>(
account_id,
Collection::Email,
&deletion_candidates,
Property::Value,
)
.await?
{
if data.unarchive::<MessageData>()?.change_id < reference_cid {
destroy_ids.insert(document_id);
}
}
self.get_archives(
account_id,
Collection::Email,
&deletion_candidates,
Property::Value,
|document_id, data| {
if data.unarchive::<MessageData>()?.change_id < reference_cid {
destroy_ids.insert(document_id);
}
Ok(true)
},
)
.await?;
if destroy_ids.is_empty() {
return Ok(());