From 428af52e2fa073febd9fdc0835dfd1e32b04ef0a Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sat, 30 May 2026 17:59:21 +0200 Subject: [PATCH] fix: remove staging chunks after completed uploads Successful completion moved the assembled file into data/complete but left the upload staging directory behind, including all chunk files. Remove the upload's staging directory only after the final file has been renamed into place so incomplete and failed uploads remain resumable. A repeat complete request for that old upload id now returns 404 because the temporary upload record has been retired with its chunks. Test Plan: - just check Refs: none --- TESTS.md | 3 ++- src/storage.rs | 9 +++++++++ tests/completion.rs | 6 +++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/TESTS.md b/TESTS.md index c795e19..7bf5694 100644 --- a/TESTS.md +++ b/TESTS.md @@ -17,7 +17,8 @@ Keep this file as the reusable verification checklist while implementing - `PUT /api/uploads/:id/chunks/:index` rejects out-of-range indexes. - `PUT /api/uploads/:id/chunks/:index` accepts duplicate chunks. - `GET /api/uploads/:id` reports completed chunks from disk. - - `POST /api/uploads/:id/complete` assembles verified chunks. + - `POST /api/uploads/:id/complete` assembles verified chunks and removes + staging data. - `POST /api/uploads/:id/complete` rejects incomplete uploads. - `POST /api/uploads/:id/complete` rejects corrupt chunk files. - `static/app.js` passes `node --check`. diff --git a/src/storage.rs b/src/storage.rs index 8116dee..82b049b 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -171,6 +171,7 @@ impl Storage { drop(output); fs::rename(&tmp_path, &final_path).await?; + self.remove_upload_dir(upload_id).await?; Ok(meta.complete_response(final_path.display().to_string())) } @@ -257,6 +258,14 @@ impl Storage { Ok(()) } + + async fn remove_upload_dir(&self, upload_id: &str) -> Result<(), StorageError> { + match fs::remove_dir_all(self.upload_dir(upload_id)).await { + Ok(()) => Ok(()), + Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()), + Err(error) => Err(error.into()), + } + } } #[derive(Debug)] diff --git a/tests/completion.rs b/tests/completion.rs index e917071..089a452 100644 --- a/tests/completion.rs +++ b/tests/completion.rs @@ -48,11 +48,11 @@ async fn assembles_completed_upload() -> Result<(), Box> b"hello world" ); assert!( - temp_dir + !temp_dir .path() .join("staging") .join(&upload.upload_id) - .is_dir() + .exists() ); let duplicate = app @@ -61,7 +61,7 @@ async fn assembles_completed_upload() -> Result<(), Box> &format!("/api/uploads/{}/complete", upload.upload_id), )?) .await?; - assert_eq!(duplicate.status(), StatusCode::CONFLICT); + assert_eq!(duplicate.status(), StatusCode::NOT_FOUND); Ok(()) }