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
This commit is contained in:
@@ -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` rejects out-of-range indexes.
|
||||||
- `PUT /api/uploads/:id/chunks/:index` accepts duplicate chunks.
|
- `PUT /api/uploads/:id/chunks/:index` accepts duplicate chunks.
|
||||||
- `GET /api/uploads/:id` reports completed chunks from disk.
|
- `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 incomplete uploads.
|
||||||
- `POST /api/uploads/:id/complete` rejects corrupt chunk files.
|
- `POST /api/uploads/:id/complete` rejects corrupt chunk files.
|
||||||
- `static/app.js` passes `node --check`.
|
- `static/app.js` passes `node --check`.
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ impl Storage {
|
|||||||
drop(output);
|
drop(output);
|
||||||
|
|
||||||
fs::rename(&tmp_path, &final_path).await?;
|
fs::rename(&tmp_path, &final_path).await?;
|
||||||
|
self.remove_upload_dir(upload_id).await?;
|
||||||
|
|
||||||
Ok(meta.complete_response(final_path.display().to_string()))
|
Ok(meta.complete_response(final_path.display().to_string()))
|
||||||
}
|
}
|
||||||
@@ -257,6 +258,14 @@ impl Storage {
|
|||||||
|
|
||||||
Ok(())
|
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)]
|
#[derive(Debug)]
|
||||||
|
|||||||
+3
-3
@@ -48,11 +48,11 @@ async fn assembles_completed_upload() -> Result<(), Box<dyn std::error::Error>>
|
|||||||
b"hello world"
|
b"hello world"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
temp_dir
|
!temp_dir
|
||||||
.path()
|
.path()
|
||||||
.join("staging")
|
.join("staging")
|
||||||
.join(&upload.upload_id)
|
.join(&upload.upload_id)
|
||||||
.is_dir()
|
.exists()
|
||||||
);
|
);
|
||||||
|
|
||||||
let duplicate = app
|
let duplicate = app
|
||||||
@@ -61,7 +61,7 @@ async fn assembles_completed_upload() -> Result<(), Box<dyn std::error::Error>>
|
|||||||
&format!("/api/uploads/{}/complete", upload.upload_id),
|
&format!("/api/uploads/{}/complete", upload.upload_id),
|
||||||
)?)
|
)?)
|
||||||
.await?;
|
.await?;
|
||||||
assert_eq!(duplicate.status(), StatusCode::CONFLICT);
|
assert_eq!(duplicate.status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user