Merge pull request #570 from swissiety/rfc-4331-quota-properties
feat(webdav): RFC 4331 quota-available-bytes/quota-used-bytes
This commit is contained in:
@@ -557,6 +557,7 @@ async fn handle_propfind(
|
||||
created_by: None,
|
||||
updated_by: None,
|
||||
};
|
||||
let quota = state.resolve_webdav_quota(user.id, Uuid::nil()).await;
|
||||
return build_streaming_propfind_response(
|
||||
root_folder,
|
||||
None, // folder_id = None → root children (drive-root folders)
|
||||
@@ -567,6 +568,7 @@ async fn handle_propfind(
|
||||
file_retrieval_service,
|
||||
user.id,
|
||||
state.webdav_dead_props.clone(),
|
||||
quota,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -595,6 +597,7 @@ async fn handle_propfind(
|
||||
)
|
||||
.await?;
|
||||
let folder_id = folder.id.clone();
|
||||
let quota = state.resolve_webdav_quota(user.id, drive_id).await;
|
||||
return build_streaming_propfind_response(
|
||||
folder,
|
||||
Some(folder_id),
|
||||
@@ -605,6 +608,7 @@ async fn handle_propfind(
|
||||
file_retrieval_service,
|
||||
user.id,
|
||||
state.webdav_dead_props.clone(),
|
||||
quota,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -659,6 +663,7 @@ async fn handle_propfind(
|
||||
)
|
||||
.await?;
|
||||
let folder_id = folder.id.clone();
|
||||
let quota = state.resolve_webdav_quota(user.id, drive_id).await;
|
||||
return build_streaming_propfind_response(
|
||||
folder,
|
||||
Some(folder_id),
|
||||
@@ -669,6 +674,7 @@ async fn handle_propfind(
|
||||
file_retrieval_service,
|
||||
user.id,
|
||||
state.webdav_dead_props.clone(),
|
||||
quota,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -732,6 +738,7 @@ async fn build_streaming_propfind_response(
|
||||
file_retrieval_service: std::sync::Arc<FileRetrievalService>,
|
||||
user_id: Uuid,
|
||||
dead_props_store: Arc<DeadPropertyStore>,
|
||||
quota: Option<(i64, Option<i64>)>,
|
||||
) -> Result<Response<Body>, AppError> {
|
||||
let depth = depth.to_string();
|
||||
let base_href = base_href.to_string();
|
||||
@@ -752,7 +759,7 @@ async fn build_streaming_propfind_response(
|
||||
let mut w = Writer::new(&mut buf);
|
||||
WebDavAdapter::write_multistatus_start(&mut w)
|
||||
.map_err(|e| std::io::Error::other(e.to_string()))?;
|
||||
WebDavAdapter::write_folder_entry_with_dead_props(&mut w, &folder, &propfind_request, &base_href, &folder_dead)
|
||||
WebDavAdapter::write_folder_entry_with_dead_props(&mut w, &folder, &propfind_request, &base_href, &folder_dead, quota)
|
||||
.map_err(|e| std::io::Error::other(e.to_string()))?;
|
||||
}
|
||||
yield Bytes::from(buf);
|
||||
@@ -795,7 +802,7 @@ async fn build_streaming_propfind_response(
|
||||
let mut w = Writer::new(&mut chunk);
|
||||
for (subfolder, child_dead) in result.items.iter().zip(subfolder_deads.iter()) {
|
||||
let href = format!("{}{}/", base_href, encode_path_segment(&subfolder.name));
|
||||
WebDavAdapter::write_folder_entry_with_dead_props(&mut w, subfolder, &propfind_request, &href, child_dead)
|
||||
WebDavAdapter::write_folder_entry_with_dead_props(&mut w, subfolder, &propfind_request, &href, child_dead, quota)
|
||||
.map_err(|e| std::io::Error::other(e.to_string()))?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,6 +213,10 @@ async fn handle_filter_files(
|
||||
(fid, oc_id.as_deref()),
|
||||
&user.username,
|
||||
&favorite_ids,
|
||||
// REPORT results are a flat filter/search listing, not a
|
||||
// PROPFIND on a specific collection — quota isn't
|
||||
// meaningful here (see `AppState::resolve_webdav_quota`).
|
||||
None,
|
||||
&dead,
|
||||
)
|
||||
.map_err(|e| AppError::internal_error(format!("XML write error: {}", e)))?;
|
||||
@@ -346,6 +350,10 @@ async fn handle_search(
|
||||
(fid, oc_id.as_deref()),
|
||||
&user.username,
|
||||
&favorite_ids,
|
||||
// REPORT results are a flat filter/search listing, not a
|
||||
// PROPFIND on a specific collection — quota isn't
|
||||
// meaningful here (see `AppState::resolve_webdav_quota`).
|
||||
None,
|
||||
&dead,
|
||||
)
|
||||
.map_err(|e| AppError::internal_error(format!("XML write error: {}", e)))?;
|
||||
|
||||
@@ -341,6 +341,7 @@ async fn handle_propfind(
|
||||
// function's username arg. Refining the owner-id usages
|
||||
// back to the canonical username is deferred to the
|
||||
// NcSession commit.
|
||||
let quota = state.resolve_webdav_quota(user.id, chroot.drive_id).await;
|
||||
Ok(build_nc_streaming_propfind(
|
||||
state.clone(),
|
||||
folder,
|
||||
@@ -348,6 +349,7 @@ async fn handle_propfind(
|
||||
user.id,
|
||||
url_user.to_string(),
|
||||
subpath.to_string(),
|
||||
quota,
|
||||
))
|
||||
}
|
||||
ResolvedResource::File(file) => {
|
||||
@@ -1481,6 +1483,7 @@ fn build_nc_streaming_propfind(
|
||||
user_id: Uuid,
|
||||
username: String,
|
||||
subpath: String,
|
||||
quota: Option<(i64, Option<i64>)>,
|
||||
) -> Response<Body> {
|
||||
let stream = async_stream::try_stream! {
|
||||
let file_id_svc = state.nextcloud.as_ref().map(|n| &n.file_ids);
|
||||
@@ -1509,7 +1512,7 @@ fn build_nc_streaming_propfind(
|
||||
let href = nc_collection_href(&username, &subpath);
|
||||
let fid = folder_id_map.get(&folder.id).copied();
|
||||
let oc_id = fid.map(|id| format_oc_id(id, file_id_svc));
|
||||
write_folder_response(&mut xml, &folder, &href, (fid, oc_id.as_deref()), &username, &folder_favs, &folder_dead)
|
||||
write_folder_response(&mut xml, &folder, &href, (fid, oc_id.as_deref()), &username, &folder_favs, quota, &folder_dead)
|
||||
.map_err(std::io::Error::other)?;
|
||||
}
|
||||
yield Bytes::from(buf);
|
||||
@@ -1608,7 +1611,7 @@ fn build_nc_streaming_propfind(
|
||||
let href = nc_collection_href(&username, &child_sub);
|
||||
let fid = sub_id_map.get(&sf.id).copied();
|
||||
let oc_id = fid.map(|id| format_oc_id(id, file_id_svc));
|
||||
write_folder_response(&mut xml, sf, &href, (fid, oc_id.as_deref()), &username, &favs, dead)
|
||||
write_folder_response(&mut xml, sf, &href, (fid, oc_id.as_deref()), &username, &favs, quota, dead)
|
||||
.map_err(std::io::Error::other)?;
|
||||
}
|
||||
}
|
||||
@@ -1647,6 +1650,7 @@ fn build_nc_streaming_propfind(
|
||||
/// together (`oc_id` is derived from `file_id`) — to stay under
|
||||
/// clippy's argument-count lint now that `dead_props` is also threaded
|
||||
/// through.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn write_folder_response<W: std::io::Write>(
|
||||
xml: &mut Writer<W>,
|
||||
folder: &FolderDto,
|
||||
@@ -1654,6 +1658,7 @@ pub fn write_folder_response<W: std::io::Write>(
|
||||
oc_ids: (Option<i64>, Option<&str>),
|
||||
owner: &str,
|
||||
favorite_ids: &HashSet<String>,
|
||||
quota: Option<(i64, Option<i64>)>,
|
||||
dead_props: &[(QualifiedName, Option<String>)],
|
||||
) -> Result<(), String> {
|
||||
let (file_id, oc_id) = oc_ids;
|
||||
@@ -1705,6 +1710,16 @@ pub fn write_folder_response<W: std::io::Write>(
|
||||
// Numeric share-permissions bitmask: Read=1 + Update=2 + Create=4 + Delete=8 + Share=16 = 31
|
||||
write_text_element(xml, "ocs:share-permissions", "31")?;
|
||||
write_text_element(xml, "oc:size", "0")?;
|
||||
// RFC 4331 — same account/drive-wide value regardless of which
|
||||
// folder entry is being described, mirroring the native WebDAV
|
||||
// surface's `write_folder_standard_props` (see
|
||||
// `AppState::resolve_webdav_quota`).
|
||||
if let Some((used, available)) = quota {
|
||||
write_text_element(xml, "d:quota-used-bytes", &used.to_string())?;
|
||||
if let Some(avail) = available {
|
||||
write_text_element(xml, "d:quota-available-bytes", &avail.to_string())?;
|
||||
}
|
||||
}
|
||||
write_text_element(xml, "oc:owner-id", owner)?;
|
||||
write_text_element(xml, "oc:owner-display-name", owner)?;
|
||||
write_text_element(xml, "nc:has-preview", "false")?;
|
||||
|
||||
Reference in New Issue
Block a user