diff --git a/src/application/services/file_upload_service.rs b/src/application/services/file_upload_service.rs index a6ac8209..f3eb48f7 100644 --- a/src/application/services/file_upload_service.rs +++ b/src/application/services/file_upload_service.rs @@ -353,16 +353,24 @@ impl FileUploadService { /// the target drive is `kind='personal'`, so a shared-drive upload /// still doesn't touch any user envelope. fn maybe_update_storage_usage(&self, file: &FileDto, caller_id: Uuid) { + self.apply_storage_usage_delta(file.size as i64, &file.folder_id, caller_id); + } + + /// Same as [`Self::maybe_update_storage_usage`] but takes an explicit + /// `delta` instead of assuming "whole file size" — the overwrite path + /// (`update_file_streaming_with_perms`) needs `new_size - old_size`, + /// not the new size added a second time on top of what the old + /// content already contributed. + fn apply_storage_usage_delta(&self, delta: i64, folder_id: &Option, caller_id: Uuid) { let Some(storage_service) = &self.storage_usage_service else { return; }; - let delta = file.size as i64; + if delta == 0 { + return; + } let owner = Some(caller_id); - let folder = file - .folder_id - .as_deref() - .and_then(|s| Uuid::parse_str(s).ok()); + let folder = folder_id.as_deref().and_then(|s| Uuid::parse_str(s).ok()); // Per-user delta — only when the target drive is `kind='personal'`. // The user envelope (`auth.users.storage_quota_bytes`) caps the SUM @@ -496,6 +504,7 @@ impl FileUploadUseCase for FileUploadService { ) .await?; + let old_size = file.size(); let file_id = file.id().to_string(); let (new_hash, updated_at) = self .file_write @@ -531,6 +540,11 @@ impl FileUploadUseCase for FileUploadService { DomainError::internal_error("FileUpload", format!("rebuild entity: {e}")) })?; let dto = FileDto::from(updated); + self.apply_storage_usage_delta( + blob.size as i64 - old_size as i64, + &dto.folder_id, + caller_id, + ); if let Some(hook) = &self.file_lifecycle_hook { hook.on_file_updated(&file_id, &dto.content_hash, content_type); } @@ -603,6 +617,7 @@ impl FileUploadUseCase for FileUploadService { ) .await?; let dto = FileDto::from(created); + self.maybe_update_storage_usage(&dto, caller_id); if let Some(hook) = &self.file_lifecycle_hook { hook.on_file_created(&dto.id, &dto.content_hash, content_type, is_new_blob); } diff --git a/tests/api/nc_webdav_quota_properties.hurl b/tests/api/nc_webdav_quota_properties.hurl index 928f14c4..99775a07 100644 --- a/tests/api/nc_webdav_quota_properties.hurl +++ b/tests/api/nc_webdav_quota_properties.hurl @@ -120,8 +120,10 @@ ncq_root_folder_id: jsonpath "$.root_folder_id" # ───────────────────────────────────────────────────────────── PROPFIND {{base_url}}/remote.php/dav/files/{{ncq_nc_username}}~{{ncq_root_folder_id}}/ Depth: 0 +[Options] +variable: ncq_composite_user={{ncq_nc_username}}~{{ncq_root_folder_id}} [BasicAuth] -{{ncq_nc_username}}~{{ncq_root_folder_id}}: {{ncq_nc_password}} +{{ncq_composite_user}}: {{ncq_nc_password}} HTTP 207 [Asserts] @@ -135,8 +137,10 @@ xpath "number(//*[local-name()='quota-available-bytes'])" == 500 # ───────────────────────────────────────────────────────────── PUT {{base_url}}/remote.php/dav/files/{{ncq_nc_username}}~{{ncq_root_folder_id}}/quota-probe.txt Content-Type: text/plain +[Options] +variable: ncq_composite_user={{ncq_nc_username}}~{{ncq_root_folder_id}} [BasicAuth] -{{ncq_nc_username}}~{{ncq_root_folder_id}}: {{ncq_nc_password}} +{{ncq_composite_user}}: {{ncq_nc_password}} ``` 32-byte-ish payload for nc ``` @@ -144,10 +148,18 @@ Content-Type: text/plain HTTP 201 +# The drive-usage bump is fire-and-forget on a tokio task (see +# `file_upload_service.rs::maybe_update_storage_usage`), so retry +# until `used_bytes` catches up — same shape as `drive_quota.hurl` +# Step 5. PROPFIND {{base_url}}/remote.php/dav/files/{{ncq_nc_username}}~{{ncq_root_folder_id}}/ Depth: 0 +[Options] +variable: ncq_composite_user={{ncq_nc_username}}~{{ncq_root_folder_id}} +retry: 10 +retry-interval: 200ms [BasicAuth] -{{ncq_nc_username}}~{{ncq_root_folder_id}}: {{ncq_nc_password}} +{{ncq_composite_user}}: {{ncq_nc_password}} HTTP 207 [Asserts] diff --git a/tests/api/webdav_quota_properties.hurl b/tests/api/webdav_quota_properties.hurl index e7d48f93..d092578f 100644 --- a/tests/api/webdav_quota_properties.hurl +++ b/tests/api/webdav_quota_properties.hurl @@ -203,10 +203,18 @@ Content-Type: text/plain HTTP 201 +# The drive-usage bump is fire-and-forget on a tokio task (see +# `file_upload_service.rs::maybe_update_storage_usage`), so the SQL +# UPDATE may not have landed yet when the PUT above returned. Retry +# the PROPFIND until `used_bytes` catches up — same shape as +# `drive_quota.hurl` Step 5. PROPFIND {{base_url}}/webdav/@drive/{{wq_drive_id}}/ Authorization: Bearer {{wq_owner_token}} Depth: 0 Content-Type: application/xml; charset=utf-8 +[Options] +retry: 10 +retry-interval: 200ms ```