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

@@ -159,39 +159,40 @@ impl<T: SessionStream> SessionData<T> {
let mut special_uses = AHashMap::new();
let mut mailbox_topology = TopologicalSort::with_capacity(10);
for (mailbox_id, mailbox_) in self
.server
.get_properties::<Archive<AlignedBytes>, _>(
self.server
.get_archives(
account_id,
Collection::Mailbox,
&mailbox_ids,
Property::Value,
|mailbox_id, mailbox_| {
let mailbox = mailbox_
.unarchive::<email::mailbox::Mailbox>()
.caused_by(trc::location!())?;
// Map special uses
let role = SpecialUse::from(&mailbox.role);
if !matches!(mailbox.role, ArchivedSpecialUse::None) {
special_uses.insert(role, mailbox_id);
}
// Build mailbox data
let mailbox = MailboxData {
mailbox_id,
parent_id: u32::from(mailbox.parent_id),
role,
name: mailbox.name.to_string(),
is_subscribed: mailbox.is_subscribed(access_token.primary_id()),
};
mailbox_topology.insert(mailbox.parent_id, mailbox.mailbox_id + 1);
// Add mailbox id
mailboxes.insert(mailbox.mailbox_id, mailbox);
Ok(true)
},
)
.await
.caused_by(trc::location!())?
{
let mailbox = mailbox_
.unarchive::<email::mailbox::Mailbox>()
.caused_by(trc::location!())?;
// Map special uses
let role = SpecialUse::from(&mailbox.role);
if !matches!(mailbox.role, ArchivedSpecialUse::None) {
special_uses.insert(role, mailbox_id);
}
// Build mailbox data
let mailbox = MailboxData {
mailbox_id,
parent_id: u32::from(mailbox.parent_id),
role,
name: mailbox.name.to_string(),
is_subscribed: mailbox.is_subscribed(access_token.primary_id()),
};
mailbox_topology.insert(mailbox.parent_id, mailbox.mailbox_id + 1);
// Add mailbox id
mailboxes.insert(mailbox.mailbox_id, mailbox);
}
.caused_by(trc::location!())?;
// Build account
let message_ids = self

View File

@@ -50,40 +50,40 @@ impl<T: SessionStream> SessionData<T> {
// Obtain all message ids
let mut uid_map = BTreeMap::new();
for (message_id, message_data_) in self
.server
.get_properties::<Archive<AlignedBytes>, _>(
self.server
.get_archives(
mailbox.account_id,
Collection::Email,
&message_ids,
Property::Value,
|message_id, message_data_| {
let message_data = message_data_
.unarchive::<MessageData>()
.caused_by(trc::location!())?;
// Make sure the message is still in this mailbox
if let Some(item) = message_data
.mailboxes
.iter()
.find(|item| item.mailbox_id == mailbox.mailbox_id)
{
debug_assert!(item.uid != 0, "UID is zero for message {item:?}");
if uid_map.insert(u32::from(item.uid), message_id).is_some() {
trc::event!(
Store(trc::StoreEvent::UnexpectedError),
AccountId = mailbox.account_id,
Collection = Collection::Mailbox,
MailboxId = mailbox.mailbox_id,
MessageId = message_id,
SpanId = self.session_id,
Details = "Duplicate IMAP UID"
);
}
}
Ok(true)
},
)
.await?
.into_iter()
{
let message_data = message_data_
.unarchive::<MessageData>()
.caused_by(trc::location!())?;
// Make sure the message is still in this mailbox
if let Some(item) = message_data
.mailboxes
.iter()
.find(|item| item.mailbox_id == mailbox.mailbox_id)
{
debug_assert!(item.uid != 0, "UID is zero for message {item:?}");
if uid_map.insert(u32::from(item.uid), message_id).is_some() {
trc::event!(
Store(trc::StoreEvent::UnexpectedError),
AccountId = mailbox.account_id,
Collection = Collection::Mailbox,
MailboxId = mailbox.mailbox_id,
MessageId = message_id,
SpanId = self.session_id,
Details = "Duplicate IMAP UID"
);
}
}
}
.await?;
// Obtain UID next and assign UIDs
let mut uid_max = 0;

View File

@@ -24,7 +24,7 @@ use jmap_proto::types::{
};
use store::{
roaring::RoaringBitmap,
write::{AlignedBytes, Archive, BatchBuilder, log::ChangeLogBuilder},
write::{BatchBuilder, log::ChangeLogBuilder},
};
use super::{ImapContext, ToModSeq};
@@ -189,53 +189,62 @@ impl<T: SessionStream> SessionData<T> {
changelog: &mut ChangeLogBuilder,
) -> trc::Result<()> {
let mut destroy_ids = RoaringBitmap::new();
let mut batch = BatchBuilder::new();
batch
.with_account_id(account_id)
.with_collection(Collection::Email);
for (id, data_) in self
.server
.get_properties::<Archive<AlignedBytes>, _>(
self.server
.get_archives(
account_id,
Collection::Email,
deleted_ids,
Property::Value,
|id, data_| {
let data = data_
.to_unarchived::<MessageData>()
.caused_by(trc::location!())?;
if !data.inner.has_mailbox_id(mailbox_id) {
return Ok(true);
} else if data.inner.mailboxes.len() == 1 {
destroy_ids.insert(id);
return Ok(true);
}
// Prepare changes
let mut new_data = data.deserialize().caused_by(trc::location!())?;
if changelog.change_id == u64::MAX {
changelog.change_id = self.server.assign_change_id(account_id)?
}
new_data.change_id = changelog.change_id;
let thread_id = new_data.thread_id;
// Untag message from this mailbox and remove Deleted flag
new_data.remove_mailbox(mailbox_id);
new_data.remove_keyword(&Keyword::Deleted);
changelog.log_update(Collection::Email, Id::from_parts(thread_id, id));
changelog.log_child_update(Collection::Mailbox, mailbox_id);
// Write changes
batch
.update_document(id)
.custom(
ObjectIndexBuilder::new()
.with_current(data)
.with_changes(new_data),
)
.caused_by(trc::location!())?;
Ok(true)
},
)
.await
.caused_by(trc::location!())?
{
let data = data_
.to_unarchived::<MessageData>()
.caused_by(trc::location!())?;
.caused_by(trc::location!())?;
if !data.inner.has_mailbox_id(mailbox_id) {
continue;
} else if data.inner.mailboxes.len() == 1 {
destroy_ids.insert(id);
continue;
}
// Prepare changes
let mut new_data = data.deserialize().caused_by(trc::location!())?;
if changelog.change_id == u64::MAX {
changelog.change_id = self.server.assign_change_id(account_id)?
}
new_data.change_id = changelog.change_id;
let thread_id = new_data.thread_id;
// Untag message from this mailbox and remove Deleted flag
new_data.remove_mailbox(mailbox_id);
new_data.remove_keyword(&Keyword::Deleted);
// Write changes
let mut batch = BatchBuilder::new();
batch
.with_account_id(account_id)
.with_collection(Collection::Email)
.update_document(id)
.custom(
ObjectIndexBuilder::new()
.with_current(data)
.with_changes(new_data),
)
.caused_by(trc::location!())?;
if !batch.is_empty() {
match self
.server
.store()
@@ -243,10 +252,7 @@ impl<T: SessionStream> SessionData<T> {
.await
.caused_by(trc::location!())
{
Ok(_) => {
changelog.log_update(Collection::Email, Id::from_parts(thread_id, id));
changelog.log_child_update(Collection::Mailbox, mailbox_id);
}
Ok(_) => {}
Err(err) => {
if !err.is_assertion_failure() {
return Err(err.caused_by(trc::location!()));