Import upstream v0.16.22, stripped

Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f
Enterprise-only files removed or emptied: 63
Enterprise-only snippets removed: 117 in 50 files
Dangling module declarations removed: 5
Cargo edits turning enterprise off: 14
Verification: clean
Enterprise feature gates left for rebuilt features: 19 in 18 files

Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
+469
View File
@@ -0,0 +1,469 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
api::acl::JmapAcl,
blob::download::BlobDownload,
changes::state::JmapCacheState,
file::set::{
Collision, NoResolver, fetch_existing_modified, find_sibling_collision, pick_unique_rename,
update_file_node, validate_file_node_hierarchy,
},
};
use common::{Server, auth::AccessToken, sharing::EffectiveAcl};
use groupware::{cache::GroupwareCache, file::FileNode};
use http_proto::HttpSessionData;
use jmap_proto::{
error::set::SetError,
method::{
copy::{CopyRequest, CopyResponse},
set::SetRequest,
},
object::file_node::{self, FileNodeProperty, OnExists},
request::{
Call, IntoValid, MaybeInvalid, RequestMethod, SetRequestMethod,
method::{MethodFunction, MethodName, MethodObject},
reference::MaybeResultReference,
},
types::state::State,
};
use store::{
ValueKey,
ahash::{AHashMap, AHashSet},
roaring::RoaringBitmap,
write::{AlignedBytes, Archive, BatchBuilder, now},
};
use trc::AddContext;
use types::{
acl::Acl,
collection::{Collection, SyncCollection},
};
use utils::map::vec_map::VecMap;
pub trait FileNodeCopy: Sync + Send {
fn file_node_copy<'x>(
&self,
request: CopyRequest<'x, file_node::FileNode>,
access_token: &AccessToken,
next_call: &mut Option<Call<RequestMethod<'x>>>,
session: &HttpSessionData,
) -> impl Future<Output = trc::Result<CopyResponse<file_node::FileNode>>> + Send;
}
impl FileNodeCopy for Server {
async fn file_node_copy<'x>(
&self,
request: CopyRequest<'x, file_node::FileNode>,
access_token: &AccessToken,
next_call: &mut Option<Call<RequestMethod<'x>>>,
_session: &HttpSessionData,
) -> trc::Result<CopyResponse<file_node::FileNode>> {
let account_id = request.account_id.document_id();
let from_account_id = request.from_account_id.document_id();
if account_id == from_account_id {
return Err(trc::JmapEvent::InvalidArguments
.into_err()
.details("From accountId is equal to fromAccountId"));
}
let cache = self
.fetch_dav_resources(
access_token.account_id(),
account_id,
SyncCollection::FileNode,
)
.await
.caused_by(trc::location!())?;
let old_state = cache.assert_state(false, &request.if_in_state)?;
let mut response = CopyResponse {
from_account_id: request.from_account_id,
account_id: request.account_id,
new_state: old_state.clone(),
old_state,
created: VecMap::with_capacity(request.create.len()),
not_created: VecMap::new(),
};
let from_cache = self
.fetch_dav_resources(
access_token.account_id(),
from_account_id,
SyncCollection::FileNode,
)
.await
.caused_by(trc::location!())?;
let from_node_ids = if access_token.is_member(from_account_id) {
from_cache
.resources
.iter()
.map(|r| r.document_id)
.collect::<RoaringBitmap>()
} else {
let mut readable =
from_cache.shared_containers(access_token, [Acl::Read, Acl::ReadItems], true);
readable |= from_cache.shared_items(access_token, [Acl::ReadItems], true);
readable
};
let is_shared = access_token.is_shared(account_id);
let can_add_to = if is_shared {
Some(cache.shared_containers(access_token, [Acl::AddItems], true))
} else {
None
};
let on_exists = request.arguments.on_exists;
let case_insensitive = request
.arguments
.compare_case_insensitively
.unwrap_or(false);
let on_destroy_remove_children = request
.arguments
.on_destroy_remove_children
.unwrap_or(false);
let on_success_delete = request.on_success_destroy_original.unwrap_or(false);
let mut batch = BatchBuilder::new();
let mut pending_names: AHashMap<(u32, String), Option<u32>> = AHashMap::new();
let mut implicit_destroys: AHashSet<u32> = AHashSet::new();
let mut created_folders = AHashMap::new();
let mut destroy_ids = Vec::new();
'create: for (id, create) in request.create.into_valid() {
let from_document_id = id.document_id();
if !from_node_ids.contains(from_document_id) {
response.not_created.append(
id,
SetError::not_found().with_description(format!(
"Item {} not found in account {}.",
id, response.from_account_id
)),
);
continue;
}
let Some(source) = self
.store()
.get_value::<Archive<AlignedBytes>>(ValueKey::archive(
from_account_id,
Collection::FileNode,
from_document_id,
))
.await
.caused_by(trc::location!())?
else {
response.not_created.append(
id,
SetError::not_found().with_description(format!(
"Item {} not found in account {}.",
id, response.from_account_id
)),
);
continue;
};
let mut file_node = source
.deserialize::<FileNode>()
.caused_by(trc::location!())?;
// ACLs are account-scoped; do not carry the source account's grants over.
file_node.acls.clear();
let has_acl_changes =
match update_file_node(None, create, &mut file_node, true, &NoResolver) {
Ok(result) => {
if let Some(blob_id) = result.blob_id {
let file_details = file_node.file.get_or_insert_default();
if !self.has_access_blob(&blob_id, access_token).await? {
response.not_created.append(
id,
SetError::forbidden().with_description(format!(
"You do not have access to blobId {blob_id}."
)),
);
continue 'create;
} else if let Some(blob_contents) = self
.blob_store()
.get_blob(blob_id.hash.as_slice(), 0..usize::MAX)
.await?
{
file_details.size = blob_contents.len() as u32;
} else {
response.not_created.append(
id,
SetError::invalid_properties()
.with_property(FileNodeProperty::BlobId)
.with_description("Blob could not be found."),
);
continue 'create;
}
file_details.blob_hash = blob_id.hash;
}
if file_node
.file
.as_ref()
.is_some_and(|f| f.blob_hash.is_empty())
{
response.not_created.append(
id,
SetError::invalid_properties()
.with_property(FileNodeProperty::BlobId)
.with_description("Missing blob id."),
);
continue 'create;
}
result.has_acl_changes
}
Err(err) => {
response.not_created.append(id, err);
continue 'create;
}
};
if let Err(err) =
validate_file_node_hierarchy(None, &file_node, is_shared, &cache, &created_folders)
{
response.not_created.append(id, err);
continue 'create;
}
if file_node.modified == 0 {
file_node.modified = now() as i64;
}
let renamed = match find_sibling_collision(
None,
&file_node,
&cache,
&pending_names,
case_insensitive,
) {
Collision::None => false,
Collision::Existing(existing) => {
let effective = match on_exists {
OnExists::Newest => {
let existing_modified =
fetch_existing_modified(self.store(), account_id, existing).await?;
if file_node.modified > existing_modified {
OnExists::Replace
} else {
response.not_created.append(
id,
SetError::already_exists()
.with_existing_id(types::id::Id::from(existing)),
);
continue 'create;
}
}
other => other,
};
match effective {
OnExists::Reject => {
response.not_created.append(
id,
SetError::already_exists()
.with_existing_id(types::id::Id::from(existing)),
);
continue 'create;
}
OnExists::Rename => {
file_node.name = pick_unique_rename(
&file_node.name,
None,
file_node.parent_id,
&cache,
&pending_names,
case_insensitive,
);
true
}
OnExists::Replace => {
if let Some(target) = cache.any_resource_path_by_id(existing) {
let subtree_len = cache.subtree(target.path()).count();
if subtree_len > 1 && !on_destroy_remove_children {
response
.not_created
.append(id, SetError::node_has_children());
continue 'create;
}
}
implicit_destroys.insert(existing);
false
}
OnExists::Newest => unreachable!(),
}
}
Collision::Pending => match on_exists {
OnExists::Rename => {
file_node.name = pick_unique_rename(
&file_node.name,
None,
file_node.parent_id,
&cache,
&pending_names,
case_insensitive,
);
true
}
OnExists::Reject | OnExists::Replace | OnExists::Newest => {
let key = crate::file::set::pending_key(&file_node, case_insensitive);
let mut err = SetError::already_exists();
if let Some(Some(doc_id)) = pending_names.get(&key) {
err = err.with_existing_id(types::id::Id::from(*doc_id));
}
response.not_created.append(id, err);
continue 'create;
}
},
};
// Permission and ACL inheritance for the destination parent
if file_node.parent_id > 0 {
let parent_id = file_node.parent_id - 1;
// The user must be allowed to add children to the destination parent
if let Some(allowed) = &can_add_to
&& !created_folders.contains_key(&parent_id)
&& !allowed.contains(parent_id)
{
response.not_created.append(
id,
SetError::forbidden().with_description(
"You are not allowed to create file nodes in this folder.",
),
);
continue 'create;
}
let parent_acls = created_folders.get(&parent_id).cloned().or_else(|| {
cache
.container_resource_by_id(parent_id)
.and_then(|r| r.acls())
.map(|a| a.to_vec())
});
if !has_acl_changes {
if let Some(parent_acls) = parent_acls {
file_node.acls = parent_acls;
}
} else if is_shared
&& parent_acls
.is_none_or(|acls| !acls.effective_acl(access_token).contains(Acl::Share))
{
response.not_created.append(
id,
SetError::forbidden()
.with_description("You are not allowed to share this file node."),
);
continue 'create;
}
} else if is_shared {
response.not_created.append(
id,
SetError::forbidden()
.with_description("Cannot create top-level folder in a shared account."),
);
continue 'create;
}
if !file_node.acls.is_empty() {
if let Err(err) = self.acl_validate(&file_node.acls).await {
response.not_created.append(id, err.into());
continue 'create;
}
self.refresh_acls(&file_node.acls, None)
.await
.caused_by(trc::location!())?;
}
let document_id = self
.store()
.assign_document_ids(account_id, Collection::FileNode, 1)
.await
.caused_by(trc::location!())?;
if file_node.file.is_none() {
created_folders.insert(document_id, file_node.acls.clone());
}
pending_names.insert(
crate::file::set::pending_key(&file_node, case_insensitive),
None,
);
let final_name = file_node.name.clone();
let set_created = file_node.created == 0;
let set_modified = file_node.modified == 0;
file_node
.insert(
access_token.account_tenant_ids(),
account_id,
document_id,
set_created,
set_modified,
&mut batch,
)
.caused_by(trc::location!())?;
response.created(id, document_id);
if renamed
&& let Some(value) = response.created.get_mut(&id)
&& let jmap_tools::Value::Object(map) = value
{
map.insert_unchecked(
jmap_tools::Key::Property(FileNodeProperty::Name),
jmap_tools::Value::Str(std::borrow::Cow::Owned(final_name)),
);
}
if on_success_delete {
destroy_ids.push(MaybeInvalid::Value(id));
}
}
for did in &implicit_destroys {
let Some(node) = cache.any_resource_path_by_id(*did) else {
continue;
};
let mut ids = cache.subtree(node.path()).collect::<Vec<_>>();
ids.sort_unstable_by_key(|b| std::cmp::Reverse(b.hierarchy_seq()));
let sorted = ids.into_iter().map(|a| a.document_id()).collect::<Vec<_>>();
groupware::DestroyArchive(sorted)
.delete_batch(
self,
access_token.account_tenant_ids(),
account_id,
cache.format_resource(node).into(),
&mut batch,
)
.await
.caused_by(trc::location!())?;
}
if !batch.is_empty() {
let change_id = self
.commit_batch(batch)
.await
.and_then(|ids| ids.last_change_id(account_id))
.caused_by(trc::location!())?;
response.new_state = State::Exact(change_id);
}
if on_success_delete && !destroy_ids.is_empty() {
*next_call = Call {
id: String::new(),
name: MethodName::new(MethodObject::FileNode, MethodFunction::Set),
method: RequestMethod::Set(SetRequestMethod::FileNode(Box::new(SetRequest {
account_id: request.from_account_id,
if_in_state: request.destroy_from_if_in_state,
create: None,
update: None,
destroy: MaybeResultReference::Value(destroy_ids).into(),
arguments: Default::default(),
}))),
}
.into();
}
Ok(response)
}
}
+309
View File
@@ -0,0 +1,309 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{api::acl::JmapRights, changes::state::JmapCacheState};
use common::{Server, auth::AccessToken, sharing::EffectiveAcl};
use groupware::{cache::GroupwareCache, file::FileNode};
use jmap_proto::{
method::get::{GetRequest, GetResponse},
object::file_node::{self, FileNodeNodeType, FileNodeProperty, FileNodeValue},
types::date::UTCDate,
};
use jmap_tools::{Map, Value};
use store::{
ValueKey,
roaring::RoaringBitmap,
write::{AlignedBytes, Archive, now},
};
use trc::AddContext;
use types::{
acl::{Acl, AclGrant},
blob::{BlobClass, BlobId},
blob_hash::BlobHash,
collection::{Collection, SyncCollection},
};
pub trait FileNodeGet: Sync + Send {
fn file_node_get(
&self,
request: GetRequest<file_node::FileNode>,
access_token: &AccessToken,
) -> impl Future<Output = trc::Result<GetResponse<file_node::FileNode>>> + Send;
}
impl FileNodeGet for Server {
async fn file_node_get(
&self,
mut request: GetRequest<file_node::FileNode>,
access_token: &AccessToken,
) -> trc::Result<GetResponse<file_node::FileNode>> {
let (ids, not_found_ids) = request.unwrap_ids(self.core.jmap.get_max_objects)?;
let properties = request.unwrap_properties(&[
FileNodeProperty::Id,
FileNodeProperty::ParentId,
FileNodeProperty::NodeType,
FileNodeProperty::BlobId,
FileNodeProperty::Target,
FileNodeProperty::Size,
FileNodeProperty::Name,
FileNodeProperty::Type,
FileNodeProperty::Created,
FileNodeProperty::Modified,
FileNodeProperty::Accessed,
FileNodeProperty::Changed,
FileNodeProperty::Executable,
FileNodeProperty::IsSubscribed,
FileNodeProperty::MyRights,
FileNodeProperty::ShareWith,
FileNodeProperty::Role,
]);
let account_id = request.account_id.document_id();
let cache = self
.fetch_dav_resources(
access_token.account_id(),
account_id,
SyncCollection::FileNode,
)
.await?;
// TODO: draft-14 section 5 case 2 - ancestors of shared nodes should be discoverable with mayRead=false
let file_node_ids = if access_token.is_member(account_id) {
cache
.resources
.iter()
.map(|r| r.document_id)
.collect::<RoaringBitmap>()
} else {
cache.shared_documents(access_token, [Acl::Read, Acl::ReadItems], true)
};
let mut ids = if let Some(ids) = ids {
ids
} else {
file_node_ids
.iter()
.take(self.core.jmap.get_max_objects)
.map(Into::into)
.collect::<Vec<_>>()
};
if request.arguments.fetch_parents.unwrap_or(false) {
let mut seen: RoaringBitmap = ids.iter().map(|i| i.document_id()).collect();
let mut extra: Vec<types::id::Id> = Vec::new();
for id in &ids {
let mut current = cache
.any_resource_path_by_id(id.document_id())
.and_then(|r| r.parent_id());
while let Some(parent_id) = current {
if !seen.insert(parent_id) {
break;
}
if file_node_ids.contains(parent_id) {
extra.push(parent_id.into());
}
current = cache
.container_resource_by_id(parent_id)
.and_then(|r| r.parent_id());
}
}
ids.extend(extra);
}
let mut response = GetResponse {
account_id: request.account_id.into(),
state: cache.get_state(false).into(),
list: Vec::with_capacity(ids.len()),
not_found: not_found_ids,
};
for id in ids {
// Obtain the file_node object
let document_id = id.document_id();
if !file_node_ids.contains(document_id) {
response.push_not_found(id);
continue;
}
let _file_node = if let Some(file_node) = self
.store()
.get_value::<Archive<AlignedBytes>>(ValueKey::archive(
account_id,
Collection::FileNode,
document_id,
))
.await?
{
file_node
} else {
response.push_not_found(id);
continue;
};
let file_node = _file_node
.unarchive::<FileNode>()
.caused_by(trc::location!())?;
let mut result = Map::with_capacity(properties.len());
for property in &properties {
match property {
FileNodeProperty::Id => {
result.insert_unchecked(FileNodeProperty::Id, FileNodeValue::Id(id));
}
FileNodeProperty::Name => {
result.insert_unchecked(FileNodeProperty::Name, file_node.name.to_string());
}
FileNodeProperty::ShareWith => {
result.insert_unchecked(
FileNodeProperty::ShareWith,
JmapRights::share_with::<file_node::FileNode>(
account_id,
access_token,
&file_node
.acls
.iter()
.map(AclGrant::from)
.collect::<Vec<_>>(),
),
);
}
FileNodeProperty::MyRights => {
result.insert_unchecked(
FileNodeProperty::MyRights,
if access_token.is_shared(account_id) {
JmapRights::rights::<file_node::FileNode>(
file_node.acls.effective_acl(access_token),
)
} else {
JmapRights::all_rights::<file_node::FileNode>()
},
);
}
FileNodeProperty::ParentId => {
let parent_id = file_node.parent_id.to_native();
result.insert_unchecked(
FileNodeProperty::ParentId,
if parent_id > 0 {
Value::Element(FileNodeValue::Id((parent_id - 1).into()))
} else {
Value::Null
},
);
}
FileNodeProperty::BlobId => {
result.insert_unchecked(
FileNodeProperty::BlobId,
if let Some(file) = file_node.file.as_ref() {
Value::Element(FileNodeValue::BlobId(BlobId::new(
BlobHash::from(&file.blob_hash),
BlobClass::Linked {
account_id,
collection: Collection::FileNode.into(),
document_id: id.document_id(),
},
)))
} else {
Value::Null
},
);
}
FileNodeProperty::Size => {
result.insert_unchecked(
FileNodeProperty::Size,
if let Some(file) = file_node.file.as_ref() {
Value::Number(file.size.to_native().into())
} else {
Value::Null
},
);
}
FileNodeProperty::Type => {
result.insert_unchecked(
FileNodeProperty::Type,
if let Some(file) = file_node.file.as_ref() {
Value::Str(
file.media_type
.as_ref()
.map(|t| t.to_string())
.unwrap_or_else(|| "application/octet-stream".to_string())
.into(),
)
} else {
Value::Null
},
);
}
FileNodeProperty::Executable => {
result.insert_unchecked(
FileNodeProperty::Executable,
if let Some(file) = file_node.file.as_ref() {
Value::Bool(file.executable)
} else {
Value::Null
},
);
}
FileNodeProperty::Created => {
result.insert_unchecked(
FileNodeProperty::Created,
Value::Element(FileNodeValue::Date(UTCDate::from_timestamp(
file_node.created.to_native(),
))),
);
}
FileNodeProperty::Modified => {
result.insert_unchecked(
FileNodeProperty::Modified,
Value::Element(FileNodeValue::Date(UTCDate::from_timestamp(
file_node.modified.to_native(),
))),
);
}
FileNodeProperty::Accessed => {
// TODO: needs serialization change (per-user accessed timestamp); returns now() as a placeholder
result.insert_unchecked(
FileNodeProperty::Accessed,
Value::Element(FileNodeValue::Date(UTCDate::from_timestamp(
now() as i64
))),
);
}
FileNodeProperty::Changed => {
// TODO: needs serialization change (dedicated server-set changed timestamp); returns modified as a placeholder
result.insert_unchecked(
FileNodeProperty::Changed,
Value::Element(FileNodeValue::Date(UTCDate::from_timestamp(
file_node.modified.to_native(),
))),
);
}
FileNodeProperty::NodeType => {
let node_type = if file_node.file.is_some() {
FileNodeNodeType::File
} else {
FileNodeNodeType::Directory
};
result.insert_unchecked(
FileNodeProperty::NodeType,
Value::Str(node_type.as_str().into()),
);
}
FileNodeProperty::Target => {
result.insert_unchecked(FileNodeProperty::Target, Value::Null);
}
FileNodeProperty::Role => {
result.insert_unchecked(FileNodeProperty::Role, Value::Null);
}
FileNodeProperty::IsSubscribed => {
// TODO: needs serialization change (per-user subscription state); always true for now
result.insert_unchecked(FileNodeProperty::IsSubscribed, Value::Bool(true));
}
property => {
result.insert_unchecked(property.clone(), Value::Null);
}
}
}
response.list.push(result.into());
}
Ok(response)
}
}
+10
View File
@@ -0,0 +1,10 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod copy;
pub mod get;
pub mod query;
pub mod set;
+280
View File
@@ -0,0 +1,280 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{api::query::QueryResponseBuilder, changes::state::JmapCacheState};
use common::{Server, auth::AccessToken};
use groupware::cache::GroupwareCache;
use jmap_proto::{
method::query::{Filter, QueryRequest, QueryResponse},
object::file_node::{FileNode, FileNodeComparator, FileNodeFilter},
request::MaybeInvalid,
};
use store::{
ahash::AHashMap,
roaring::RoaringBitmap,
search::{SearchFilter, SearchQuery},
write::SearchIndex,
};
use types::{acl::Acl, collection::SyncCollection};
pub trait FileNodeQuery: Sync + Send {
fn file_node_query(
&self,
request: QueryRequest<FileNode>,
access_token: &AccessToken,
) -> impl Future<Output = trc::Result<QueryResponse>> + Send;
}
impl FileNodeQuery for Server {
async fn file_node_query(
&self,
mut request: QueryRequest<FileNode>,
access_token: &AccessToken,
) -> trc::Result<QueryResponse> {
let account_id = request.account_id.document_id();
let mut filters = Vec::with_capacity(request.filter.len());
let cache = self
.fetch_dav_resources(
access_token.account_id(),
account_id,
SyncCollection::FileNode,
)
.await?;
for cond in std::mem::take(&mut request.filter) {
match cond {
Filter::Property(cond) => match cond {
FileNodeFilter::AncestorId(MaybeInvalid::Value(id)) => {
if let Some(resource) =
cache.container_resource_path_by_id(id.document_id())
{
filters.push(SearchFilter::is_in_set(RoaringBitmap::from_iter(
cache.subtree(resource.path()).map(|r| r.document_id()),
)))
} else {
filters.push(SearchFilter::is_in_set(RoaringBitmap::new()));
}
}
FileNodeFilter::DescendantId(MaybeInvalid::Value(id)) => {
let mut ancestors = RoaringBitmap::new();
let mut current = cache
.any_resource_path_by_id(id.document_id())
.and_then(|r| r.parent_id());
while let Some(parent_id) = current {
if !ancestors.insert(parent_id) {
break;
}
current = cache
.container_resource_by_id(parent_id)
.and_then(|r| r.parent_id());
}
filters.push(SearchFilter::is_in_set(ancestors));
}
FileNodeFilter::ParentId(MaybeInvalid::Value(id)) => {
filters.push(SearchFilter::is_in_set(RoaringBitmap::from_iter(
cache.children_ids(id.document_id()),
)));
}
FileNodeFilter::IsTopLevel(is_top_level) => {
filters.push(SearchFilter::is_in_set(RoaringBitmap::from_iter(
cache.resources.iter().filter_map(|r| {
if is_top_level == r.parent_id().is_none() {
Some(r.document_id)
} else {
None
}
}),
)));
}
FileNodeFilter::NodeType(node_type) => {
let want_container = match node_type.as_str() {
"directory" => Some(true),
"file" => Some(false),
_ => None,
};
let set = match want_container {
Some(is_container) => {
RoaringBitmap::from_iter(cache.resources.iter().filter_map(|r| {
if r.is_container() == is_container {
Some(r.document_id)
} else {
None
}
}))
}
// TODO: support symlink nodeType once target storage exists
None => RoaringBitmap::new(),
};
filters.push(SearchFilter::is_in_set(set));
}
FileNodeFilter::Name(name) => {
filters.push(SearchFilter::is_in_set(RoaringBitmap::from_iter(
cache.resources.iter().filter_map(|r| {
if r.container_name().is_some_and(|n| n == name) {
Some(r.document_id)
} else {
None
}
}),
)));
}
FileNodeFilter::NameMatch(name) => {
filters.push(SearchFilter::is_in_set(RoaringBitmap::from_iter(
cache.resources.iter().filter_map(|r| {
if r.container_name().is_some_and(|n| name.matches(n)) {
Some(r.document_id)
} else {
None
}
}),
)));
}
FileNodeFilter::MinSize(size) => {
let size = size as u32;
filters.push(SearchFilter::is_in_set(RoaringBitmap::from_iter(
cache.resources.iter().filter_map(|r| {
if r.size().is_some_and(|s| s >= size) {
Some(r.document_id)
} else {
None
}
}),
)));
}
FileNodeFilter::MaxSize(size) => {
let size = size as u32;
filters.push(SearchFilter::is_in_set(RoaringBitmap::from_iter(
cache.resources.iter().filter_map(|r| {
if r.size().is_some_and(|s| s <= size) {
Some(r.document_id)
} else {
None
}
}),
)));
}
// TODO: filters below require fetching archives or new indexes; ignore for now
FileNodeFilter::Role(_)
| FileNodeFilter::HasAnyRole(_)
| FileNodeFilter::BlobId(_)
| FileNodeFilter::IsExecutable(_)
| FileNodeFilter::CreatedBefore(_)
| FileNodeFilter::CreatedAfter(_)
| FileNodeFilter::ModifiedBefore(_)
| FileNodeFilter::ModifiedAfter(_)
| FileNodeFilter::AccessedBefore(_)
| FileNodeFilter::AccessedAfter(_)
| FileNodeFilter::Type(_)
| FileNodeFilter::TypeMatch(_)
| FileNodeFilter::Text(_)
| FileNodeFilter::Body(_)
| FileNodeFilter::AncestorId(_)
| FileNodeFilter::DescendantId(_)
| FileNodeFilter::ParentId(_)
| FileNodeFilter::_T(_) => {}
},
Filter::And => {
filters.push(SearchFilter::And);
}
Filter::Or => {
filters.push(SearchFilter::Or);
}
Filter::Not => {
filters.push(SearchFilter::Not);
}
Filter::Close => {
filters.push(SearchFilter::End);
}
}
}
let results = SearchQuery::new(SearchIndex::InMemory)
.with_filters(filters)
.with_mask(if access_token.is_shared(account_id) {
cache.shared_documents(access_token, [Acl::Read, Acl::ReadItems], true)
} else {
cache.resources.iter().map(|r| r.document_id).collect()
})
.filter()
.into_bitmap();
let mut response = QueryResponseBuilder::new(
results.len() as usize,
self.core.jmap.query_max_results,
cache.get_state(false),
&request,
);
// Only name, size and nodeType can be sorted from the cache.
// TODO: created/modified/type/tree sorts require archive or hierarchy traversal
let sortable = request
.sort
.as_deref()
.unwrap_or_default()
.iter()
.filter(|c| {
matches!(
c.property,
FileNodeComparator::Name
| FileNodeComparator::Size
| FileNodeComparator::NodeType
)
})
.collect::<Vec<_>>();
if sortable.is_empty() {
for document_id in results {
if !response.add(0, document_id) {
break;
}
}
} else {
let by_id = cache
.resources
.iter()
.map(|r| (r.document_id, r))
.collect::<AHashMap<_, _>>();
let mut ids = results.iter().collect::<Vec<_>>();
ids.sort_unstable_by(|a, b| {
for cmp in &sortable {
let ra = by_id.get(a);
let rb = by_id.get(b);
let ordering = match cmp.property {
FileNodeComparator::Name => ra
.and_then(|r| r.container_name())
.cmp(&rb.and_then(|r| r.container_name())),
FileNodeComparator::Size => {
ra.and_then(|r| r.size()).cmp(&rb.and_then(|r| r.size()))
}
FileNodeComparator::NodeType => {
// Directories sort before files
let a_dir = ra.map(|r| r.is_container()).unwrap_or(false);
let b_dir = rb.map(|r| r.is_container()).unwrap_or(false);
b_dir.cmp(&a_dir)
}
_ => std::cmp::Ordering::Equal,
};
let ordering = if cmp.is_ascending {
ordering
} else {
ordering.reverse()
};
if ordering != std::cmp::Ordering::Equal {
return ordering;
}
}
a.cmp(b)
});
for document_id in ids {
if !response.add(0, document_id) {
break;
}
}
}
response.build()
}
}
File diff suppressed because it is too large Load Diff