Groupware caching improvements
This commit is contained in:
@@ -116,9 +116,23 @@ impl Caches {
|
||||
+ (1024 * std::mem::size_of::<MessageUidCache>())
|
||||
+ (15 * (std::mem::size_of::<MailboxCache>() + 60))) as u64,
|
||||
),
|
||||
dav: Cache::from_config(
|
||||
files: Cache::from_config(
|
||||
config,
|
||||
"dav",
|
||||
"files",
|
||||
MB_10,
|
||||
(std::mem::size_of::<DavResources>() + (500 * std::mem::size_of::<DavResource>()))
|
||||
as u64,
|
||||
),
|
||||
events: Cache::from_config(
|
||||
config,
|
||||
"events",
|
||||
MB_10,
|
||||
(std::mem::size_of::<DavResources>() + (500 * std::mem::size_of::<DavResource>()))
|
||||
as u64,
|
||||
),
|
||||
contacts: Cache::from_config(
|
||||
config,
|
||||
"contacts",
|
||||
MB_10,
|
||||
(std::mem::size_of::<DavResources>() + (500 * std::mem::size_of::<DavResource>()))
|
||||
as u64,
|
||||
|
||||
@@ -33,7 +33,7 @@ use nlp::bayes::{TokenHash, Weights};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use rustls::sign::CertifiedKey;
|
||||
use std::{
|
||||
hash::{BuildHasher, Hasher},
|
||||
hash::{BuildHasher, Hash, Hasher},
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
sync::{
|
||||
Arc,
|
||||
@@ -46,7 +46,6 @@ use tinyvec::TinyVec;
|
||||
use tokio::sync::{Notify, Semaphore, mpsc};
|
||||
use tokio_rustls::TlsConnector;
|
||||
use utils::{
|
||||
bimap::{IdBimap, IdBimapItem},
|
||||
cache::{Cache, CacheItemWeight, CacheWithTtl},
|
||||
snowflake::SnowflakeIdGenerator,
|
||||
};
|
||||
@@ -149,7 +148,9 @@ pub struct Caches {
|
||||
pub permissions: Cache<u32, Arc<RolePermissions>>,
|
||||
|
||||
pub messages: Cache<u32, CacheSwap<MessageStoreCache>>,
|
||||
pub dav: Cache<DavResourceId, Arc<DavResources>>,
|
||||
pub files: Cache<u32, CacheSwap<DavResources>>,
|
||||
pub contacts: Cache<u32, CacheSwap<DavResources>>,
|
||||
pub events: Cache<u32, CacheSwap<DavResources>>,
|
||||
|
||||
pub bayes: CacheWithTtl<TokenHash, Weights>,
|
||||
|
||||
@@ -242,44 +243,72 @@ pub struct TlsConnectors {
|
||||
|
||||
pub struct NameWrapper(pub String);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub struct DavResourceId {
|
||||
pub account_id: u32,
|
||||
pub collection: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DavResources {
|
||||
pub base_path: String,
|
||||
pub paths: IdBimap<DavResource>,
|
||||
pub paths: AHashSet<DavPath>,
|
||||
pub resources: Vec<DavResource>,
|
||||
pub item_change_id: u64,
|
||||
pub container_change_id: u64,
|
||||
pub highest_change_id: u64,
|
||||
pub size: u64,
|
||||
pub modseq: Option<u64>,
|
||||
pub update_lock: Arc<Semaphore>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DavPath {
|
||||
pub path: String,
|
||||
pub parent_id: Option<u32>,
|
||||
pub hierarchy_seq: u32,
|
||||
pub resource_idx: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DavResource {
|
||||
pub document_id: u32,
|
||||
pub parent_id: Option<u32>,
|
||||
pub name: String,
|
||||
pub data: DavResourceMetadata,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DavResourcePath<'x> {
|
||||
pub path: &'x DavPath,
|
||||
pub resource: &'x DavResource,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DavResourceMetadata {
|
||||
File {
|
||||
size: u32,
|
||||
hierarchy_sequence: u32,
|
||||
is_container: bool,
|
||||
name: String,
|
||||
size: Option<u32>,
|
||||
parent_id: Option<u32>,
|
||||
acls: TinyVec<[AclGrant; 2]>,
|
||||
},
|
||||
Calendar {
|
||||
name: String,
|
||||
acls: TinyVec<[AclGrant; 2]>,
|
||||
tz: Tz,
|
||||
},
|
||||
CalendarEvent {
|
||||
names: TinyVec<[DavName; 2]>,
|
||||
start: i64,
|
||||
duration: u32,
|
||||
},
|
||||
#[default]
|
||||
None,
|
||||
AddressBook {
|
||||
name: String,
|
||||
acls: TinyVec<[AclGrant; 2]>,
|
||||
},
|
||||
ContactCard {
|
||||
names: TinyVec<[DavName; 2]>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
#[rkyv(derive(Debug))]
|
||||
pub struct DavName {
|
||||
pub name: String,
|
||||
pub parent_id: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
@@ -299,12 +328,6 @@ pub struct Core {
|
||||
pub enterprise: Option<enterprise::Enterprise>,
|
||||
}
|
||||
|
||||
impl CacheItemWeight for DavResourceId {
|
||||
fn weight(&self) -> u64 {
|
||||
std::mem::size_of::<DavResourceId>() as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CacheItemWeight> CacheItemWeight for CacheSwap<T> {
|
||||
fn weight(&self) -> u64 {
|
||||
std::mem::size_of::<CacheSwap<T>>() as u64 + self.0.load().weight()
|
||||
@@ -428,7 +451,9 @@ impl Default for Caches {
|
||||
http_auth: Cache::new(1024, 10 * 1024 * 1024),
|
||||
permissions: Cache::new(1024, 10 * 1024 * 1024),
|
||||
messages: Cache::new(1024, 25 * 1024 * 1024),
|
||||
dav: Cache::new(1024, 10 * 1024 * 1024),
|
||||
files: Cache::new(1024, 10 * 1024 * 1024),
|
||||
contacts: Cache::new(1024, 10 * 1024 * 1024),
|
||||
events: Cache::new(1024, 10 * 1024 * 1024),
|
||||
bayes: CacheWithTtl::new(1024, 10 * 1024 * 1024),
|
||||
dns_rbl: CacheWithTtl::new(1024, 10 * 1024 * 1024),
|
||||
dns_txt: CacheWithTtl::new(1024, 10 * 1024 * 1024),
|
||||
@@ -480,45 +505,117 @@ pub fn ip_to_bytes_prefix(prefix: u8, ip: &IpAddr) -> Vec<u8> {
|
||||
}
|
||||
}
|
||||
|
||||
impl DavResourcePath<'_> {
|
||||
#[inline(always)]
|
||||
pub fn document_id(&self) -> u32 {
|
||||
self.resource.document_id
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn parent_id(&self) -> Option<u32> {
|
||||
self.path.parent_id
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn path(&self) -> &str {
|
||||
self.path.path.as_str()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_container(&self) -> bool {
|
||||
self.resource.is_container()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn hierarchy_seq(&self) -> u32 {
|
||||
self.path.hierarchy_seq
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn size(&self) -> u32 {
|
||||
self.resource.size()
|
||||
}
|
||||
}
|
||||
|
||||
impl DavResources {
|
||||
pub fn subtree(&self, search_path: &str) -> impl Iterator<Item = &DavResource> {
|
||||
let prefix = format!("{search_path}/");
|
||||
self.paths
|
||||
pub fn by_path(&self, name: &str) -> Option<DavResourcePath<'_>> {
|
||||
self.paths.get(name).map(|path| DavResourcePath {
|
||||
path,
|
||||
resource: &self.resources[path.resource_idx],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn container_resource_by_id(&self, id: u32) -> Option<&DavResource> {
|
||||
self.resources
|
||||
.iter()
|
||||
.filter(move |item| item.name.starts_with(&prefix) || item.name == search_path)
|
||||
.find(|res| res.document_id == id && res.is_container())
|
||||
}
|
||||
|
||||
pub fn subtree(&self, search_path: &str) -> impl Iterator<Item = DavResourcePath<'_>> {
|
||||
let prefix = format!("{search_path}/");
|
||||
self.paths.iter().filter_map(move |path| {
|
||||
if path.path.starts_with(&prefix) || path.path == search_path {
|
||||
Some(DavResourcePath {
|
||||
path,
|
||||
resource: &self.resources[path.resource_idx],
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn subtree_with_depth(
|
||||
&self,
|
||||
search_path: &str,
|
||||
depth: usize,
|
||||
) -> impl Iterator<Item = &DavResource> {
|
||||
) -> impl Iterator<Item = DavResourcePath<'_>> {
|
||||
let prefix = format!("{search_path}/");
|
||||
self.paths.iter().filter(move |item| {
|
||||
item.name
|
||||
self.paths.iter().filter_map(move |path| {
|
||||
if path
|
||||
.path
|
||||
.strip_prefix(&prefix)
|
||||
.is_some_and(|name| name.as_bytes().iter().filter(|&&c| c == b'/').count() < depth)
|
||||
|| item.name == search_path
|
||||
|| path.path.as_str() == search_path
|
||||
{
|
||||
Some(DavResourcePath {
|
||||
path,
|
||||
resource: &self.resources[path.resource_idx],
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn tree_with_depth(&self, depth: usize) -> impl Iterator<Item = &DavResource> {
|
||||
self.paths.iter().filter(move |item| {
|
||||
item.name.as_bytes().iter().filter(|&&c| c == b'/').count() <= depth
|
||||
pub fn tree_with_depth(&self, depth: usize) -> impl Iterator<Item = DavResourcePath<'_>> {
|
||||
self.paths.iter().filter_map(move |path| {
|
||||
if path.path.as_bytes().iter().filter(|&&c| c == b'/').count() <= depth {
|
||||
Some(DavResourcePath {
|
||||
path,
|
||||
resource: &self.resources[path.resource_idx],
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn children(&self, parent_id: u32) -> impl Iterator<Item = &DavResource> {
|
||||
pub fn children(&self, parent_id: u32) -> impl Iterator<Item = DavResourcePath<'_>> {
|
||||
self.paths
|
||||
.iter()
|
||||
.filter(move |item| item.parent_id.is_some_and(|id| id == parent_id))
|
||||
.map(|path| DavResourcePath {
|
||||
path,
|
||||
resource: &self.resources[path.resource_idx],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn format_resource(&self, resource: &DavResource) -> String {
|
||||
if resource.is_container() {
|
||||
format!("{}{}/", self.base_path, resource.name)
|
||||
pub fn format_resource(&self, resource: DavResourcePath<'_>) -> String {
|
||||
if resource.resource.is_container() {
|
||||
format!("{}{}/", self.base_path, resource.path.path)
|
||||
} else {
|
||||
format!("{}{}", self.base_path, resource.name)
|
||||
format!("{}{}", self.base_path, resource.path.path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,59 +629,128 @@ impl DavResources {
|
||||
}
|
||||
|
||||
impl DavResource {
|
||||
pub fn is_child_of(&self, parent_id: u32) -> bool {
|
||||
match &self.data {
|
||||
DavResourceMetadata::File { parent_id: id, .. } => id.is_some_and(|id| id == parent_id),
|
||||
DavResourceMetadata::CalendarEvent { names, .. } => {
|
||||
names.iter().any(|name| name.parent_id == parent_id)
|
||||
}
|
||||
DavResourceMetadata::ContactCard { names } => {
|
||||
names.iter().any(|name| name.parent_id == parent_id)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn child_names(&self) -> Option<&[DavName]> {
|
||||
match &self.data {
|
||||
DavResourceMetadata::CalendarEvent { names, .. } => Some(names.as_slice()),
|
||||
DavResourceMetadata::ContactCard { names } => Some(names.as_slice()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn container_name(&self) -> Option<&str> {
|
||||
match &self.data {
|
||||
DavResourceMetadata::File { name, .. } => Some(name.as_str()),
|
||||
DavResourceMetadata::Calendar { name, .. } => Some(name.as_str()),
|
||||
DavResourceMetadata::AddressBook { name, .. } => Some(name.as_str()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_hierarchy_changes(&self, other: &DavResource) -> bool {
|
||||
match (&self.data, &other.data) {
|
||||
(
|
||||
DavResourceMetadata::File {
|
||||
name: a,
|
||||
parent_id: c,
|
||||
..
|
||||
},
|
||||
DavResourceMetadata::File {
|
||||
name: b,
|
||||
parent_id: d,
|
||||
..
|
||||
},
|
||||
) => a != b || c != d,
|
||||
(
|
||||
DavResourceMetadata::Calendar { name: a, .. },
|
||||
DavResourceMetadata::Calendar { name: b, .. },
|
||||
) => a != b,
|
||||
(
|
||||
DavResourceMetadata::AddressBook { name: a, .. },
|
||||
DavResourceMetadata::AddressBook { name: b, .. },
|
||||
) => a != b,
|
||||
(
|
||||
DavResourceMetadata::CalendarEvent { names: a, .. },
|
||||
DavResourceMetadata::CalendarEvent { names: b, .. },
|
||||
) => a != b,
|
||||
(
|
||||
DavResourceMetadata::ContactCard { names: a, .. },
|
||||
DavResourceMetadata::ContactCard { names: b, .. },
|
||||
) => a != b,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event_time_range(&self) -> Option<(i64, i64)> {
|
||||
match &self.data {
|
||||
DavResourceMetadata::CalendarEvent { start, duration } => {
|
||||
Some((*start, *start + *duration as i64))
|
||||
}
|
||||
DavResourceMetadata::CalendarEvent {
|
||||
start, duration, ..
|
||||
} => Some((*start, *start + *duration as i64)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn timezone(&self) -> Option<Tz> {
|
||||
match &self.data {
|
||||
DavResourceMetadata::Calendar { tz } => Some(*tz),
|
||||
DavResourceMetadata::Calendar { tz, .. } => Some(*tz),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_container(&self) -> bool {
|
||||
match &self.data {
|
||||
DavResourceMetadata::File { is_container, .. } => *is_container,
|
||||
_ => self.parent_id.is_none(),
|
||||
DavResourceMetadata::File { size, .. } => size.is_none(),
|
||||
DavResourceMetadata::Calendar { .. } | DavResourceMetadata::AddressBook { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> u32 {
|
||||
match &self.data {
|
||||
DavResourceMetadata::File { size, .. } => *size,
|
||||
DavResourceMetadata::File { size, .. } => size.unwrap_or_default(),
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hierarchy_sequence(&self) -> u32 {
|
||||
pub fn acls(&self) -> Option<&[AclGrant]> {
|
||||
match &self.data {
|
||||
DavResourceMetadata::File {
|
||||
hierarchy_sequence, ..
|
||||
} => *hierarchy_sequence,
|
||||
_ => {
|
||||
if self.parent_id.is_none() {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
DavResourceMetadata::File { acls, .. } => Some(acls.as_slice()),
|
||||
DavResourceMetadata::Calendar { acls, .. } => Some(acls.as_slice()),
|
||||
DavResourceMetadata::AddressBook { acls, .. } => Some(acls.as_slice()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IdBimapItem for DavResource {
|
||||
fn id(&self) -> &u32 {
|
||||
&self.document_id
|
||||
impl Hash for DavPath {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.path.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
impl PartialEq for DavPath {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.path == other.path
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for DavPath {}
|
||||
|
||||
impl std::borrow::Borrow<str> for DavPath {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,6 +774,12 @@ impl std::borrow::Borrow<u32> for DavResource {
|
||||
}
|
||||
}
|
||||
|
||||
impl DavName {
|
||||
pub fn new(name: String, parent_id: u32) -> Self {
|
||||
Self { name, parent_id }
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageStoreCache {
|
||||
pub fn assign_thread_id(&self, thread_name: &[u8], message_id: &[u8]) -> u32 {
|
||||
let mut bytes = Vec::with_capacity(thread_name.len() + message_id.len());
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::auth::AccessToken;
|
||||
use jmap_proto::types::{
|
||||
acl::Acl,
|
||||
value::{AclGrant, ArchivedAclGrant},
|
||||
@@ -11,10 +12,9 @@ use jmap_proto::types::{
|
||||
use rkyv::vec::ArchivedVec;
|
||||
use utils::map::bitmap::Bitmap;
|
||||
|
||||
use crate::auth::AccessToken;
|
||||
|
||||
pub mod acl;
|
||||
pub mod document;
|
||||
pub mod resources;
|
||||
|
||||
pub trait EffectiveAcl {
|
||||
fn effective_acl(&self, access_token: &AccessToken) -> Bitmap<Acl>;
|
||||
|
||||
83
crates/common/src/sharing/resources.rs
Normal file
83
crates/common/src/sharing/resources.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{DavResources, auth::AccessToken};
|
||||
use jmap_proto::types::acl::Acl;
|
||||
use store::roaring::RoaringBitmap;
|
||||
use utils::map::bitmap::Bitmap;
|
||||
|
||||
impl DavResources {
|
||||
pub fn shared_containers(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
check_acls: impl IntoIterator<Item = Acl>,
|
||||
match_any: bool,
|
||||
) -> RoaringBitmap {
|
||||
let check_acls = Bitmap::<Acl>::from_iter(check_acls);
|
||||
let mut document_ids = RoaringBitmap::new();
|
||||
|
||||
for resource in &self.resources {
|
||||
if let Some(acls) = resource.acls() {
|
||||
for acl in acls {
|
||||
if access_token.is_member(acl.account_id) {
|
||||
let mut grants = acl.grants;
|
||||
grants.intersection(&check_acls);
|
||||
if grants == check_acls || (match_any && !grants.is_empty()) {
|
||||
document_ids.insert(resource.document_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document_ids
|
||||
}
|
||||
|
||||
pub fn has_access_to_container(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
document_id: u32,
|
||||
check_acls: impl Into<Bitmap<Acl>>,
|
||||
) -> bool {
|
||||
let check_acls = check_acls.into();
|
||||
|
||||
for resource in &self.resources {
|
||||
if resource.document_id == document_id {
|
||||
if let Some(acls) = resource.acls() {
|
||||
for acl in acls {
|
||||
if access_token.is_member(acl.account_id) {
|
||||
let mut grants = acl.grants;
|
||||
grants.intersection(&check_acls);
|
||||
return !grants.is_empty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn container_acl(&self, access_token: &AccessToken, document_id: u32) -> Bitmap<Acl> {
|
||||
let mut account_acls = Bitmap::<Acl>::new();
|
||||
|
||||
for resource in &self.resources {
|
||||
if resource.document_id == document_id {
|
||||
if let Some(acls) = resource.acls() {
|
||||
for acl in acls {
|
||||
if access_token.is_member(acl.account_id) {
|
||||
account_acls.union(&acl.grants);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
account_acls
|
||||
}
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use ahash::AHashMap;
|
||||
use jmap_proto::types::{collection::Collection, property::Property};
|
||||
use store::{
|
||||
Deserialize, IndexKey, IterateParams, SerializeInfallible, SerializedVersion, U32_LEN,
|
||||
ValueKey,
|
||||
write::{AlignedBytes, Archive, ValueClass, key::DeserializeBigEndian},
|
||||
};
|
||||
use trc::AddContext;
|
||||
use utils::topological::{TopologicalSort, TopologicalSortIterator};
|
||||
|
||||
use crate::Server;
|
||||
|
||||
pub struct ExpandedFolders {
|
||||
names: AHashMap<u32, ExpandedFolder>,
|
||||
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 size: u32,
|
||||
pub hierarchy_sequence: u32,
|
||||
}
|
||||
|
||||
pub trait FolderHierarchy: Sync + Send {
|
||||
fn name(&self) -> String;
|
||||
fn parent_id(&self) -> u32;
|
||||
fn is_container(&self) -> bool;
|
||||
fn size(&self) -> u32;
|
||||
}
|
||||
|
||||
pub trait TopologyBuilder: Sync + Send {
|
||||
fn insert(&mut self, folder_id: u32, parent_id: u32);
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub async fn fetch_folders<T>(
|
||||
&self,
|
||||
account_id: u32,
|
||||
collection: Collection,
|
||||
) -> trc::Result<ExpandedFolders>
|
||||
where
|
||||
T: rkyv::Archive + SerializedVersion,
|
||||
T::Archived: FolderHierarchy
|
||||
+ for<'a> rkyv::bytecheck::CheckBytes<
|
||||
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
|
||||
> + rkyv::Deserialize<T, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
|
||||
{
|
||||
let collection_: u8 = collection.into();
|
||||
|
||||
let mut names = AHashMap::with_capacity(10);
|
||||
let mut topological_sort = TopologicalSort::with_capacity(10);
|
||||
|
||||
self.core
|
||||
.storage
|
||||
.data
|
||||
.iterate(
|
||||
IterateParams::new(
|
||||
ValueKey {
|
||||
account_id,
|
||||
collection: collection_,
|
||||
document_id: 0,
|
||||
class: ValueClass::Property(Property::Value.into()),
|
||||
},
|
||||
ValueKey {
|
||||
account_id,
|
||||
collection: collection_,
|
||||
document_id: u32::MAX,
|
||||
class: ValueClass::Property(Property::Value.into()),
|
||||
},
|
||||
),
|
||||
|key, value| {
|
||||
let document_id = key.deserialize_be_u32(key.len() - U32_LEN)?;
|
||||
let archive = <Archive<AlignedBytes> as Deserialize>::deserialize(value)?;
|
||||
let folder = archive.unarchive::<T>()?;
|
||||
let parent_id = folder.parent_id();
|
||||
|
||||
topological_sort.insert(parent_id, document_id + 1);
|
||||
names.insert(
|
||||
document_id,
|
||||
ExpandedFolder {
|
||||
name: folder.name(),
|
||||
document_id,
|
||||
parent_id: if parent_id > 0 {
|
||||
Some(parent_id - 1)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
is_container: folder.is_container(),
|
||||
size: folder.size(),
|
||||
hierarchy_sequence: 0,
|
||||
},
|
||||
);
|
||||
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.add_context(|err| {
|
||||
err.caused_by(trc::location!())
|
||||
.account_id(account_id)
|
||||
.collection(collection)
|
||||
})?;
|
||||
|
||||
Ok(ExpandedFolders {
|
||||
names,
|
||||
iter: topological_sort.into_iterator(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn fetch_folder_topology<T>(
|
||||
&self,
|
||||
account_id: u32,
|
||||
collection: Collection,
|
||||
topology: &mut impl TopologyBuilder,
|
||||
) -> trc::Result<()>
|
||||
where
|
||||
T: TopologyBuilder,
|
||||
{
|
||||
self.store()
|
||||
.iterate(
|
||||
IterateParams::new(
|
||||
IndexKey {
|
||||
account_id,
|
||||
collection: collection.into(),
|
||||
document_id: 0,
|
||||
field: Property::ParentId.into(),
|
||||
key: 0u32.serialize(),
|
||||
},
|
||||
IndexKey {
|
||||
account_id,
|
||||
collection: collection.into(),
|
||||
document_id: u32::MAX,
|
||||
field: Property::ParentId.into(),
|
||||
key: u32::MAX.serialize(),
|
||||
},
|
||||
)
|
||||
.no_values()
|
||||
.ascending(),
|
||||
|key, _| {
|
||||
let document_id = key
|
||||
.get(key.len() - U32_LEN..)
|
||||
.ok_or_else(|| trc::Error::corrupted_key(key, None, trc::location!()))
|
||||
.and_then(u32::deserialize)?;
|
||||
let parent_id = key
|
||||
.get(key.len() - (U32_LEN * 2)..key.len() - U32_LEN)
|
||||
.ok_or_else(|| trc::Error::corrupted_key(key, None, trc::location!()))
|
||||
.and_then(u32::deserialize)?;
|
||||
|
||||
topology.insert(document_id, parent_id);
|
||||
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ExpandedFolders {
|
||||
pub fn len(&self) -> usize {
|
||||
self.names.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.names.is_empty()
|
||||
}
|
||||
|
||||
pub fn format<T>(mut self, formatter: T) -> Self
|
||||
where
|
||||
T: Fn(&mut ExpandedFolder),
|
||||
{
|
||||
for folder in self.names.values_mut() {
|
||||
formatter(folder);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn into_iterator(mut self) -> impl Iterator<Item = ExpandedFolder> + Sync + Send {
|
||||
for (hierarchy_sequence, folder_id) in self.iter.by_ref().enumerate() {
|
||||
if folder_id != 0 {
|
||||
let folder_id = folder_id - 1;
|
||||
if let Some((name, parent_name)) = self
|
||||
.names
|
||||
.get(&folder_id)
|
||||
.and_then(|folder| folder.parent_id.map(|parent_id| (&folder.name, parent_id)))
|
||||
.and_then(|(name, parent_id)| {
|
||||
self.names
|
||||
.get(&parent_id)
|
||||
.map(|folder| (name, &folder.name))
|
||||
})
|
||||
{
|
||||
let name = format!("{parent_name}/{name}");
|
||||
let folder = self.names.get_mut(&folder_id).unwrap();
|
||||
folder.name = name;
|
||||
folder.hierarchy_sequence = hierarchy_sequence as u32;
|
||||
} else {
|
||||
self.names.get_mut(&folder_id).unwrap().hierarchy_sequence =
|
||||
hierarchy_sequence as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.names.into_values()
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,5 @@
|
||||
*/
|
||||
|
||||
pub mod blob;
|
||||
pub mod folder;
|
||||
pub mod index;
|
||||
pub mod state;
|
||||
|
||||
Reference in New Issue
Block a user