/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use crate::mailbox::{ArchivedMailbox, Mailbox, manage::MailboxFnc}; use common::{ MailboxCache, MailboxesCache, MessageStoreCache, Server, auth::AccessToken, sharing::EffectiveAcl, }; use store::{ ValueKey, write::{AlignedBytes, Archive}, }; use store::{ahash::AHashMap, roaring::RoaringBitmap}; use trc::AddContext; use types::{ acl::{Acl, AclGrant}, collection::Collection, special_use::SpecialUse, }; use utils::{map::bitmap::Bitmap, topological::TopologicalSort}; struct MailboxesCacheBuilder { pub change_id: u64, pub index: AHashMap, pub items: Vec, pub size: u64, } pub(crate) async fn update_mailbox_cache( server: &Server, account_id: u32, changed_ids: &AHashMap, store_cache: &MessageStoreCache, ) -> trc::Result { let mut new_cache = MailboxesCacheBuilder { items: Vec::with_capacity(store_cache.mailboxes.items.len()), index: AHashMap::with_capacity(store_cache.mailboxes.items.len()), size: 0, change_id: 0, }; for (document_id, is_update) in changed_ids { if *is_update && let Some(archive) = server .store() .get_value::>(ValueKey::archive( account_id, Collection::Mailbox, *document_id, )) .await .caused_by(trc::location!())? { insert_item( &mut new_cache, *document_id, archive.unarchive::()?, ); } } for item in store_cache.mailboxes.items.iter() { if !changed_ids.contains_key(&item.document_id) { mailbox_insert(&mut new_cache, item.clone()); } } build_tree(&mut new_cache); Ok(new_cache.build()) } pub(crate) async fn full_mailbox_cache_build( server: &Server, account_id: u32, ) -> trc::Result { // Build cache let mut cache = MailboxesCacheBuilder { items: Default::default(), index: Default::default(), size: 0, change_id: 0, }; server .archives( account_id, Collection::Mailbox, &(), |document_id, archive| { insert_item(&mut cache, document_id, archive.unarchive::()?); Ok(true) }, ) .await .caused_by(trc::location!())?; if cache.items.is_empty() { server .create_system_folders(account_id) .await .caused_by(trc::location!())?; server .archives( account_id, Collection::Mailbox, &(), |document_id, archive| { insert_item(&mut cache, document_id, archive.unarchive::()?); Ok(true) }, ) .await .caused_by(trc::location!())?; } build_tree(&mut cache); Ok(cache.build()) } fn insert_item(cache: &mut MailboxesCacheBuilder, document_id: u32, mailbox: &ArchivedMailbox) { let parent_id = mailbox.parent_id.to_native(); let item = MailboxCache { document_id, name: mailbox.name.as_str().into(), path: "".into(), role: (&mailbox.role).into(), parent_id: if parent_id > 0 { parent_id - 1 } else { u32::MAX }, sort_order: mailbox .sort_order .as_ref() .map(|s| s.to_native()) .unwrap_or(u32::MAX), subscribers: mailbox.subscribers.iter().map(|s| s.to_native()).collect(), uid_validity: mailbox.uid_validity.to_native(), acls: mailbox .acls .iter() .map(|acl| AclGrant { account_id: acl.account_id.to_native(), grants: Bitmap::from(&acl.grants), }) .collect(), }; mailbox_insert(cache, item); } fn build_tree(cache: &mut MailboxesCacheBuilder) { cache.size = 0; let mut topological_sort = TopologicalSort::with_capacity(cache.items.len()); for (idx, mailbox) in cache.items.iter_mut().enumerate() { topological_sort.insert( if mailbox.parent_id == u32::MAX { 0 } else { mailbox.parent_id + 1 }, mailbox.document_id + 1, ); mailbox.path = if matches!(mailbox.role, SpecialUse::Inbox) { "INBOX".into() } else if mailbox.is_root() && mailbox.name.as_str().eq_ignore_ascii_case("inbox") { format!("INBOX {}", idx + 1) } else { mailbox.name.clone() }; cache.size += item_size(mailbox); } for folder_id in topological_sort.into_iterator() { if folder_id != 0 { let folder_id = folder_id - 1; if let Some((path, parent_path)) = by_id(cache, &folder_id) .and_then(|folder| { folder .parent_id() .map(|parent_id| (&folder.path, parent_id)) }) .and_then(|(path, parent_id)| { by_id(cache, &parent_id).map(|folder| (path, &folder.path)) }) { let mut new_path = String::with_capacity(parent_path.len() + path.len() + 1); new_path.push_str(parent_path.as_str()); new_path.push('/'); new_path.push_str(path.as_str()); let folder = by_id_mut(cache, &folder_id).unwrap(); folder.path = new_path; } } } } impl MailboxesCacheBuilder { fn build(mut self) -> MailboxesCache { self.index.shrink_to_fit(); MailboxesCache { change_id: self.change_id, index: self.index, items: self.items.into_boxed_slice(), size: self.size, } } } pub trait MailboxCacheAccess { fn mailbox_by_id(&self, id: &u32) -> Option<&MailboxCache>; fn mailbox_by_name(&self, name: &str) -> Option<&MailboxCache>; fn mailbox_by_path(&self, name: &str) -> Option<&MailboxCache>; fn mailbox_by_role(&self, role: &SpecialUse) -> Option<&MailboxCache>; fn shared_mailboxes( &self, access_token: &AccessToken, check_acls: impl Into> + Sync + Send, ) -> RoaringBitmap; fn has_mailbox_id(&self, id: &u32) -> bool; } impl MailboxCacheAccess for MessageStoreCache { fn mailbox_by_name(&self, name: &str) -> Option<&MailboxCache> { self.mailboxes .items .iter() .find(|m| m.name.eq_ignore_ascii_case(name)) } fn mailbox_by_path(&self, path: &str) -> Option<&MailboxCache> { self.mailboxes .items .iter() .find(|m| m.path.eq_ignore_ascii_case(path)) } fn mailbox_by_role(&self, role: &SpecialUse) -> Option<&MailboxCache> { self.mailboxes.items.iter().find(|m| &m.role == role) } fn shared_mailboxes( &self, access_token: &AccessToken, check_acls: impl Into> + Sync + Send, ) -> RoaringBitmap { let check_acls = check_acls.into(); RoaringBitmap::from_iter( self.mailboxes .items .iter() .filter(|m| { m.acls .as_slice() .effective_acl(access_token) .contains_all(check_acls) }) .map(|m| m.document_id), ) } fn mailbox_by_id(&self, id: &u32) -> Option<&MailboxCache> { self.mailboxes .index .get(id) .and_then(|idx| self.mailboxes.items.get(*idx as usize)) } fn has_mailbox_id(&self, id: &u32) -> bool { self.mailboxes.index.contains_key(id) } } #[inline(always)] fn by_id<'x>(cache: &'x MailboxesCacheBuilder, id: &u32) -> Option<&'x MailboxCache> { cache .index .get(id) .and_then(|idx| cache.items.get(*idx as usize)) } #[inline(always)] fn by_id_mut<'x>(cache: &'x mut MailboxesCacheBuilder, id: &u32) -> Option<&'x mut MailboxCache> { cache .index .get(id) .and_then(|idx| cache.items.get_mut(*idx as usize)) } fn mailbox_insert(cache: &mut MailboxesCacheBuilder, item: MailboxCache) { let id = item.document_id; if let Some(idx) = cache.index.get(&id) { cache.items[*idx as usize] = item; } else { let idx = cache.items.len() as u32; cache.items.push(item); cache.index.insert(id, idx); } } #[inline(always)] fn item_size(item: &MailboxCache) -> u64 { (std::mem::size_of::() + (if item.name.len() > std::mem::size_of::() { item.name.len() } else { 0 }) + (if item.path.len() > std::mem::size_of::() { item.path.len() } else { 0 })) as u64 }