DAV file management delete
This commit is contained in:
@@ -2,6 +2,7 @@ use common::{Server, auth::AccessToken};
|
||||
use hyper::StatusCode;
|
||||
use jmap_proto::types::{acl::Acl, collection::Collection};
|
||||
use trc::AddContext;
|
||||
use utils::map::bitmap::Bitmap;
|
||||
|
||||
use crate::DavError;
|
||||
|
||||
@@ -12,8 +13,20 @@ pub(crate) trait DavAclHandler: Sync + Send {
|
||||
account_id: u32,
|
||||
collection: Collection,
|
||||
parent_id: Option<u32>,
|
||||
check_acls: Acl,
|
||||
check_acls: impl Into<Bitmap<Acl>> + Send,
|
||||
) -> impl Future<Output = crate::Result<u32>> + Send;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn validate_child_or_parent_acl(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
account_id: u32,
|
||||
collection: Collection,
|
||||
document_id: u32,
|
||||
parent_id: Option<u32>,
|
||||
child_acl: impl Into<Bitmap<Acl>> + Send,
|
||||
parent_acl: impl Into<Bitmap<Acl>> + Send,
|
||||
) -> impl Future<Output = crate::Result<()>> + Send;
|
||||
}
|
||||
|
||||
impl DavAclHandler for Server {
|
||||
@@ -23,7 +36,7 @@ impl DavAclHandler for Server {
|
||||
account_id: u32,
|
||||
collection: Collection,
|
||||
parent_id: Option<u32>,
|
||||
check_acls: Acl,
|
||||
check_acls: impl Into<Bitmap<Acl>> + Send,
|
||||
) -> crate::Result<u32> {
|
||||
match parent_id {
|
||||
Some(parent_id) => {
|
||||
@@ -53,4 +66,43 @@ impl DavAclHandler for Server {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn validate_child_or_parent_acl(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
account_id: u32,
|
||||
collection: Collection,
|
||||
document_id: u32,
|
||||
parent_id: Option<u32>,
|
||||
child_acl: impl Into<Bitmap<Acl>> + Send,
|
||||
parent_acl: impl Into<Bitmap<Acl>> + Send,
|
||||
) -> crate::Result<()> {
|
||||
if access_token.is_member(account_id)
|
||||
|| self
|
||||
.has_access_to_document(
|
||||
access_token,
|
||||
account_id,
|
||||
collection,
|
||||
document_id,
|
||||
child_acl,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
|| (parent_id.is_some()
|
||||
&& self
|
||||
.has_access_to_document(
|
||||
access_token,
|
||||
account_id,
|
||||
collection,
|
||||
parent_id.unwrap(),
|
||||
parent_acl,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?)
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(DavError::Code(StatusCode::FORBIDDEN))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,10 @@ impl DavUriResource for Server {
|
||||
.split_once("/dav/")
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
||||
|
||||
let mut uri_parts = uri_parts.splitn(3, '/').filter(|x| !x.is_empty());
|
||||
let mut uri_parts = uri_parts
|
||||
.trim_end_matches('/')
|
||||
.splitn(3, '/')
|
||||
.filter(|x| !x.is_empty());
|
||||
let mut resource = UriResource {
|
||||
collection: uri_parts
|
||||
.next()
|
||||
@@ -70,7 +73,7 @@ impl DavUriResource for Server {
|
||||
|
||||
// Obtain remaining path
|
||||
resource.account_id = Some(account_id);
|
||||
resource.resource = uri_parts.next().map(|uri| uri.trim_end_matches('/'));
|
||||
resource.resource = uri_parts.next();
|
||||
}
|
||||
|
||||
Ok(resource)
|
||||
@@ -79,6 +82,16 @@ impl DavUriResource for Server {
|
||||
|
||||
impl<T> UriResource<T> {
|
||||
pub fn account_id(&self) -> crate::Result<u32> {
|
||||
self.account_id.ok_or(DavError::Code(StatusCode::NOT_FOUND))
|
||||
self.account_id.ok_or(DavError::Code(StatusCode::FORBIDDEN))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> UriResource<Option<T>> {
|
||||
pub fn unwrap(self) -> UriResource<T> {
|
||||
UriResource {
|
||||
collection: self.collection,
|
||||
account_id: self.account_id,
|
||||
resource: self.resource.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,25 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::{Server, auth::AccessToken};
|
||||
use dav_proto::RequestHeaders;
|
||||
use std::sync::Arc;
|
||||
|
||||
use common::{Files, Server, auth::AccessToken};
|
||||
use dav_proto::{Depth, RequestHeaders};
|
||||
use groupware::file::hierarchy::FileHierarchy;
|
||||
use http_proto::HttpResponse;
|
||||
use hyper::StatusCode;
|
||||
use jmap_proto::types::{acl::Acl, collection::Collection};
|
||||
use trc::AddContext;
|
||||
use utils::map::bitmap::Bitmap;
|
||||
|
||||
use crate::{
|
||||
DavError,
|
||||
common::{
|
||||
acl::DavAclHandler,
|
||||
uri::{DavUriResource, UriResource},
|
||||
},
|
||||
file::{DavFileResource, FileItemId},
|
||||
};
|
||||
|
||||
pub(crate) trait FileCopyMoveRequestHandler: Sync + Send {
|
||||
fn handle_file_copy_move_request(
|
||||
@@ -24,6 +40,187 @@ impl FileCopyMoveRequestHandler for Server {
|
||||
headers: RequestHeaders<'_>,
|
||||
is_move: bool,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
todo!()
|
||||
// Validate source
|
||||
let from_resource = self.validate_uri(access_token, headers.uri).await?;
|
||||
let from_account_id = from_resource.account_id()?;
|
||||
let from_files = self
|
||||
.fetch_file_hierarchy(from_account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
let from_resource = from_files.map_resource::<FileItemId>(from_resource)?;
|
||||
|
||||
// Validate source ACLs
|
||||
let mut child_acl = Bitmap::new();
|
||||
let mut parent_acl = Bitmap::new();
|
||||
match (from_resource.resource.is_container, is_move) {
|
||||
(true, true) => {
|
||||
child_acl.insert(Acl::Delete);
|
||||
child_acl.insert(Acl::RemoveItems);
|
||||
parent_acl.insert(Acl::RemoveItems);
|
||||
}
|
||||
(true, false) => {
|
||||
child_acl.insert(Acl::Read);
|
||||
child_acl.insert(Acl::ReadItems);
|
||||
parent_acl.insert(Acl::ReadItems);
|
||||
}
|
||||
(false, true) => {
|
||||
child_acl.insert(Acl::Delete);
|
||||
parent_acl.insert(Acl::RemoveItems);
|
||||
}
|
||||
(false, false) => {
|
||||
child_acl.insert(Acl::Read);
|
||||
parent_acl.insert(Acl::ReadItems);
|
||||
}
|
||||
}
|
||||
self.validate_child_or_parent_acl(
|
||||
access_token,
|
||||
from_account_id,
|
||||
Collection::FileNode,
|
||||
from_resource.resource.document_id,
|
||||
from_resource.resource.parent_id,
|
||||
child_acl,
|
||||
parent_acl,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Validate destination
|
||||
let to_resource = self
|
||||
.validate_uri(
|
||||
access_token,
|
||||
headers
|
||||
.destination
|
||||
.ok_or(DavError::Code(StatusCode::BAD_GATEWAY))?,
|
||||
)
|
||||
.await?;
|
||||
let to_account_id = to_resource
|
||||
.account_id
|
||||
.ok_or(DavError::Code(StatusCode::BAD_GATEWAY))?;
|
||||
let to_files = if to_account_id == from_account_id {
|
||||
from_files.clone()
|
||||
} else {
|
||||
self.fetch_file_hierarchy(to_account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
};
|
||||
let to_resource = to_files.map_destination::<FileItemId>(to_resource)?;
|
||||
if from_resource.collection != to_resource.collection
|
||||
|| (from_resource.account_id == to_resource.account_id
|
||||
&& to_resource
|
||||
.resource
|
||||
.as_ref()
|
||||
.is_some_and(|r| r.document_id == from_resource.resource.document_id))
|
||||
{
|
||||
return Err(DavError::Code(StatusCode::BAD_GATEWAY));
|
||||
}
|
||||
|
||||
// Validate destination ACLs
|
||||
if let Some(to_resource) = &to_resource.resource {
|
||||
let mut child_acl = Bitmap::new();
|
||||
|
||||
if to_resource.is_container {
|
||||
child_acl.insert(Acl::ModifyItems);
|
||||
} else {
|
||||
child_acl.insert(Acl::Modify);
|
||||
}
|
||||
|
||||
self.validate_child_or_parent_acl(
|
||||
access_token,
|
||||
to_account_id,
|
||||
Collection::FileNode,
|
||||
to_resource.document_id,
|
||||
to_resource.parent_id,
|
||||
child_acl,
|
||||
Acl::ModifyItems,
|
||||
)
|
||||
.await?;
|
||||
} else if !access_token.is_member(to_account_id) {
|
||||
return Err(DavError::Code(StatusCode::FORBIDDEN));
|
||||
}
|
||||
|
||||
match (
|
||||
from_resource.resource.is_container,
|
||||
to_resource.resource.as_ref().is_none_or(|r| r.is_container),
|
||||
is_move,
|
||||
) {
|
||||
(true, true, true) => {
|
||||
move_container(
|
||||
self,
|
||||
from_files,
|
||||
to_files,
|
||||
from_resource,
|
||||
to_resource,
|
||||
headers.depth,
|
||||
)
|
||||
.await
|
||||
}
|
||||
(true, true, false) => {
|
||||
copy_container(
|
||||
self,
|
||||
from_files,
|
||||
to_files,
|
||||
from_resource,
|
||||
to_resource,
|
||||
headers.depth,
|
||||
)
|
||||
.await
|
||||
}
|
||||
(false, false, true) => replace_item(from_resource, to_resource.unwrap()).await,
|
||||
(false, false, false) => overwrite_item(from_resource, to_resource.unwrap()).await,
|
||||
(false, true, true) => move_item(from_resource, to_resource).await,
|
||||
(false, true, false) => copy_item(from_resource, to_resource).await,
|
||||
_ => Err(DavError::Code(StatusCode::BAD_GATEWAY)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn move_container(
|
||||
server: &Server,
|
||||
from_files: Arc<Files>,
|
||||
to_files: Arc<Files>,
|
||||
from_resource: UriResource<FileItemId>,
|
||||
to_resource: UriResource<Option<FileItemId>>,
|
||||
depth: Depth,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// check ancestors
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn copy_container(
|
||||
server: &Server,
|
||||
from_files: Arc<Files>,
|
||||
to_files: Arc<Files>,
|
||||
from_resource: UriResource<FileItemId>,
|
||||
to_resource: UriResource<Option<FileItemId>>,
|
||||
depth: Depth,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// check ancestors
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn replace_item(
|
||||
from_resource: UriResource<FileItemId>,
|
||||
to_resource: UriResource<FileItemId>,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn overwrite_item(
|
||||
from_resource: UriResource<FileItemId>,
|
||||
to_resource: UriResource<FileItemId>,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn move_item(
|
||||
from_resource: UriResource<FileItemId>,
|
||||
to_resource: UriResource<Option<FileItemId>>,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn copy_item(
|
||||
from_resource: UriResource<FileItemId>,
|
||||
to_resource: UriResource<Option<FileItemId>>,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@@ -4,9 +4,22 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::{Server, auth::AccessToken};
|
||||
use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder};
|
||||
use dav_proto::RequestHeaders;
|
||||
use groupware::file::{FileNode, hierarchy::FileHierarchy};
|
||||
use http_proto::HttpResponse;
|
||||
use hyper::StatusCode;
|
||||
use jmap_proto::types::{
|
||||
acl::Acl, collection::Collection, property::Property, type_state::DataType,
|
||||
};
|
||||
use store::write::{Archive, BatchBuilder, assert::HashedValue, log::ChangeLogBuilder};
|
||||
use trc::AddContext;
|
||||
use utils::map::bitmap::Bitmap;
|
||||
|
||||
use crate::{
|
||||
DavError,
|
||||
common::{acl::DavAclHandler, uri::DavUriResource},
|
||||
};
|
||||
|
||||
pub(crate) trait FileDeleteRequestHandler: Sync + Send {
|
||||
fn handle_file_delete_request(
|
||||
@@ -22,6 +35,97 @@ impl FileDeleteRequestHandler for Server {
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
todo!()
|
||||
// Validate URI
|
||||
let resource = self.validate_uri(access_token, headers.uri).await?;
|
||||
let account_id = resource.account_id()?;
|
||||
let delete_path = resource
|
||||
.resource
|
||||
.filter(|r| !r.is_empty())
|
||||
.ok_or(DavError::Code(StatusCode::FORBIDDEN))?;
|
||||
let files = self
|
||||
.fetch_file_hierarchy(account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
// Find ids to delete
|
||||
let mut ids = files.subtree(delete_path).collect::<Vec<_>>();
|
||||
if ids.is_empty() {
|
||||
return Err(DavError::Code(StatusCode::NOT_FOUND));
|
||||
}
|
||||
|
||||
// Sort ids descending from the deepest to the root
|
||||
ids.sort_unstable_by(|a, b| b.name.len().cmp(&a.name.len()));
|
||||
let (document_id, parent_id, is_container) = ids
|
||||
.last()
|
||||
.map(|a| (a.document_id, a.parent_id, a.is_container))
|
||||
.unwrap();
|
||||
let mut sorted_ids = Vec::with_capacity(ids.len());
|
||||
sorted_ids.extend(ids.into_iter().map(|a| a.document_id));
|
||||
|
||||
// Validate ACLs
|
||||
self.validate_child_or_parent_acl(
|
||||
access_token,
|
||||
account_id,
|
||||
Collection::FileNode,
|
||||
document_id,
|
||||
parent_id,
|
||||
if is_container {
|
||||
Bitmap::new()
|
||||
.with_item(Acl::Delete)
|
||||
.with_item(Acl::RemoveItems)
|
||||
} else {
|
||||
Bitmap::new().with_item(Acl::RemoveItems)
|
||||
},
|
||||
Acl::RemoveItems,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Process deletions
|
||||
let mut changes = ChangeLogBuilder::new();
|
||||
for document_id in sorted_ids {
|
||||
if let Some(submission) = self
|
||||
.get_property::<HashedValue<Archive>>(
|
||||
account_id,
|
||||
Collection::FileNode,
|
||||
document_id,
|
||||
Property::Value,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
// Update record
|
||||
let mut batch = BatchBuilder::new();
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::FileNode)
|
||||
.delete_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::<_, ()>::new()
|
||||
.with_tenant_id(access_token)
|
||||
.with_current(
|
||||
submission
|
||||
.to_unarchived::<FileNode>()
|
||||
.caused_by(trc::location!())?,
|
||||
),
|
||||
)
|
||||
.caused_by(trc::location!())?;
|
||||
self.store()
|
||||
.write(batch)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
changes.log_delete(Collection::FileNode, document_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Write changes
|
||||
if !changes.is_empty() {
|
||||
let change_id = self
|
||||
.commit_changes(account_id, changes)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
self.broadcast_single_state_change(account_id, change_id, DataType::FileNode)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(HttpResponse::new(StatusCode::NO_CONTENT))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use dav_proto::{
|
||||
use groupware::file::{FileNode, hierarchy::FileHierarchy};
|
||||
use http_proto::HttpResponse;
|
||||
use hyper::StatusCode;
|
||||
use jmap_proto::types::{acl::Acl, collection::Collection};
|
||||
use jmap_proto::types::{acl::Acl, collection::Collection, type_state::DataType};
|
||||
use store::write::{BatchBuilder, log::LogInsert, now};
|
||||
use trc::AddContext;
|
||||
|
||||
@@ -92,13 +92,17 @@ impl FileMkColRequestHandler for Server {
|
||||
.with_collection(Collection::FileNode)
|
||||
.create_document()
|
||||
.log(LogInsert())
|
||||
.custom(ObjectIndexBuilder::new().with_changes(node))
|
||||
.custom(ObjectIndexBuilder::<(), _>::new().with_changes(node))
|
||||
.caused_by(trc::location!())?;
|
||||
self.store()
|
||||
.write(batch)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
// Broadcast state change
|
||||
self.broadcast_single_state_change(account_id, change_id, DataType::FileNode)
|
||||
.await;
|
||||
|
||||
Ok(HttpResponse::new(StatusCode::CREATED))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use common::Files;
|
||||
use common::{FileItem, Files};
|
||||
use hyper::StatusCode;
|
||||
|
||||
use crate::{DavError, common::uri::UriResource};
|
||||
@@ -22,39 +22,58 @@ pub mod propfind;
|
||||
pub mod proppatch;
|
||||
pub mod update;
|
||||
|
||||
pub(crate) trait DavFileResource {
|
||||
fn map_resource(&self, resource: UriResource<Option<&str>>) -> crate::Result<UriResource<u32>>;
|
||||
pub(crate) trait FromFileItem {
|
||||
fn from_file_item(item: &FileItem) -> Self;
|
||||
}
|
||||
|
||||
fn map_resource_or_root(
|
||||
pub(crate) struct FileItemId {
|
||||
pub document_id: u32,
|
||||
pub parent_id: Option<u32>,
|
||||
pub is_container: bool,
|
||||
}
|
||||
|
||||
pub(crate) trait DavFileResource {
|
||||
fn map_resource<T: FromFileItem>(
|
||||
&self,
|
||||
resource: UriResource<Option<&str>>,
|
||||
) -> crate::Result<UriResource<Option<u32>>>;
|
||||
) -> crate::Result<UriResource<T>>;
|
||||
|
||||
fn map_parent<'x>(&self, resource: &'x str) -> crate::Result<(Option<u32>, Cow<'x, str>)>;
|
||||
fn map_destination<T: FromFileItem>(
|
||||
&self,
|
||||
resource: UriResource<Option<&str>>,
|
||||
) -> crate::Result<UriResource<Option<T>>>;
|
||||
|
||||
fn map_parent_resource<'x>(
|
||||
fn map_parent<'x, T: FromFileItem>(
|
||||
&self,
|
||||
resource: &'x str,
|
||||
) -> crate::Result<(Option<T>, Cow<'x, str>)>;
|
||||
|
||||
fn map_parent_resource<'x, T: FromFileItem>(
|
||||
&self,
|
||||
resource: UriResource<Option<&'x str>>,
|
||||
) -> crate::Result<UriResource<(Option<u32>, Cow<'x, str>)>>;
|
||||
) -> crate::Result<UriResource<(Option<T>, Cow<'x, str>)>>;
|
||||
}
|
||||
|
||||
impl DavFileResource for Files {
|
||||
fn map_resource(&self, resource: UriResource<Option<&str>>) -> crate::Result<UriResource<u32>> {
|
||||
fn map_resource<T: FromFileItem>(
|
||||
&self,
|
||||
resource: UriResource<Option<&str>>,
|
||||
) -> crate::Result<UriResource<T>> {
|
||||
resource
|
||||
.resource
|
||||
.and_then(|r| self.files.by_name(r))
|
||||
.map(|r| UriResource {
|
||||
collection: resource.collection,
|
||||
account_id: resource.account_id,
|
||||
resource: r,
|
||||
resource: T::from_file_item(r),
|
||||
})
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))
|
||||
}
|
||||
|
||||
fn map_resource_or_root(
|
||||
fn map_destination<T: FromFileItem>(
|
||||
&self,
|
||||
resource: UriResource<Option<&str>>,
|
||||
) -> crate::Result<UriResource<Option<u32>>> {
|
||||
) -> crate::Result<UriResource<Option<T>>> {
|
||||
Ok(UriResource {
|
||||
collection: resource.collection,
|
||||
account_id: resource.account_id,
|
||||
@@ -62,7 +81,8 @@ impl DavFileResource for Files {
|
||||
Some(
|
||||
self.files
|
||||
.by_name(resource)
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?,
|
||||
.map(T::from_file_item)
|
||||
.ok_or(DavError::Code(StatusCode::BAD_GATEWAY))?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
@@ -70,12 +90,16 @@ impl DavFileResource for Files {
|
||||
})
|
||||
}
|
||||
|
||||
fn map_parent<'x>(&self, resource: &'x str) -> crate::Result<(Option<u32>, Cow<'x, str>)> {
|
||||
fn map_parent<'x, T: FromFileItem>(
|
||||
&self,
|
||||
resource: &'x str,
|
||||
) -> crate::Result<(Option<T>, Cow<'x, str>)> {
|
||||
let (parent, child) = if let Some((parent, child)) = resource.rsplit_once('/') {
|
||||
(
|
||||
Some(
|
||||
self.files
|
||||
.by_name(parent)
|
||||
.map(T::from_file_item)
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?,
|
||||
),
|
||||
child,
|
||||
@@ -92,10 +116,10 @@ impl DavFileResource for Files {
|
||||
))
|
||||
}
|
||||
|
||||
fn map_parent_resource<'x>(
|
||||
fn map_parent_resource<'x, T: FromFileItem>(
|
||||
&self,
|
||||
resource: UriResource<Option<&'x str>>,
|
||||
) -> crate::Result<UriResource<(Option<u32>, Cow<'x, str>)>> {
|
||||
) -> crate::Result<UriResource<(Option<T>, Cow<'x, str>)>> {
|
||||
if let Some(r) = resource.resource {
|
||||
if self.files.by_name(r).is_none() {
|
||||
self.map_parent(r).map(|r| UriResource {
|
||||
@@ -111,3 +135,19 @@ impl DavFileResource for Files {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromFileItem for u32 {
|
||||
fn from_file_item(item: &FileItem) -> Self {
|
||||
item.document_id
|
||||
}
|
||||
}
|
||||
|
||||
impl FromFileItem for FileItemId {
|
||||
fn from_file_item(item: &FileItem) -> Self {
|
||||
FileItemId {
|
||||
document_id: item.document_id,
|
||||
parent_id: item.parent_id,
|
||||
is_container: item.is_container,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ use dav_proto::{
|
||||
use groupware::file::{FileNode, hierarchy::FileHierarchy};
|
||||
use http_proto::HttpResponse;
|
||||
use hyper::StatusCode;
|
||||
use jmap_proto::types::{acl::Acl, collection::Collection, property::Property};
|
||||
use jmap_proto::types::{
|
||||
acl::Acl, collection::Collection, property::Property, type_state::DataType,
|
||||
};
|
||||
use store::write::{Archive, BatchBuilder, assert::HashedValue, log::Changes, now};
|
||||
use trc::AddContext;
|
||||
|
||||
@@ -131,8 +133,9 @@ impl FilePropPatchRequestHandler for Server {
|
||||
|
||||
// Prepare write batch
|
||||
let mut batch = BatchBuilder::new();
|
||||
let change_id = new_node.change_id;
|
||||
batch
|
||||
.with_change_id(new_node.change_id)
|
||||
.with_change_id(change_id)
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::FileNode)
|
||||
.update_document(resource.resource)
|
||||
@@ -148,6 +151,10 @@ impl FilePropPatchRequestHandler for Server {
|
||||
.write(batch)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
// Broadcast state change
|
||||
self.broadcast_single_state_change(account_id, change_id, DataType::FileNode)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(HttpResponse::new(StatusCode::MULTI_STATUS)
|
||||
|
||||
@@ -9,7 +9,9 @@ use dav_proto::RequestHeaders;
|
||||
use groupware::file::{FileNode, FileProperties, hierarchy::FileHierarchy};
|
||||
use http_proto::HttpResponse;
|
||||
use hyper::StatusCode;
|
||||
use jmap_proto::types::{acl::Acl, collection::Collection, property::Property};
|
||||
use jmap_proto::types::{
|
||||
acl::Acl, collection::Collection, property::Property, type_state::DataType,
|
||||
};
|
||||
use store::write::{
|
||||
Archive, BatchBuilder,
|
||||
assert::HashedValue,
|
||||
@@ -56,7 +58,7 @@ impl FileUpdateRequestHandler for Server {
|
||||
.resource
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
||||
|
||||
if let Some(document_id) = files.files.by_name(resource_name) {
|
||||
if let Some(document_id) = files.files.by_name(resource_name).map(|r| r.document_id) {
|
||||
// Update
|
||||
let node_archive_ = self
|
||||
.get_property::<HashedValue<Archive>>(
|
||||
@@ -140,6 +142,10 @@ impl FileUpdateRequestHandler for Server {
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
// Broadcast state change
|
||||
self.broadcast_single_state_change(account_id, change_id, DataType::FileNode)
|
||||
.await;
|
||||
|
||||
Ok(HttpResponse::new(StatusCode::OK))
|
||||
} else {
|
||||
// Insert
|
||||
@@ -218,7 +224,7 @@ impl FileUpdateRequestHandler for Server {
|
||||
.create_document()
|
||||
.log(LogInsert())
|
||||
.custom(
|
||||
ObjectIndexBuilder::new()
|
||||
ObjectIndexBuilder::<(), _>::new()
|
||||
.with_changes(node)
|
||||
.with_tenant_id(access_token),
|
||||
)
|
||||
@@ -228,6 +234,10 @@ impl FileUpdateRequestHandler for Server {
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
// Broadcast state change
|
||||
self.broadcast_single_state_change(account_id, change_id, DataType::FileNode)
|
||||
.await;
|
||||
|
||||
Ok(HttpResponse::new(StatusCode::CREATED))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,23 +218,24 @@ impl DavRequestHandler for Server {
|
||||
{
|
||||
Ok(response) => response,
|
||||
Err(DavError::Internal(err)) => {
|
||||
let is_quota_error = matches!(
|
||||
err.event_type(),
|
||||
trc::EventType::Limit(trc::LimitEvent::Quota | trc::LimitEvent::TenantQuota)
|
||||
);
|
||||
let err_type = err.event_type();
|
||||
|
||||
trc::error!(err.span_id(session.session_id));
|
||||
|
||||
if is_quota_error {
|
||||
HttpResponse::new(StatusCode::PRECONDITION_FAILED)
|
||||
match err_type {
|
||||
trc::EventType::Limit(
|
||||
trc::LimitEvent::Quota | trc::LimitEvent::TenantQuota,
|
||||
) => HttpResponse::new(StatusCode::PRECONDITION_FAILED)
|
||||
.with_xml_body(
|
||||
ErrorResponse::new(BaseCondition::QuotaNotExceeded)
|
||||
.with_namespace(resource)
|
||||
.to_string(),
|
||||
)
|
||||
.with_no_cache()
|
||||
} else {
|
||||
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.with_no_cache(),
|
||||
trc::EventType::Store(trc::StoreEvent::AssertValueFailed) => {
|
||||
HttpResponse::new(StatusCode::CONFLICT)
|
||||
}
|
||||
_ => HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
Err(DavError::Parse(err)) => HttpResponse::new(StatusCode::BAD_REQUEST),
|
||||
|
||||
Reference in New Issue
Block a user