feat(files): streaming ZIP download in the project explorer
Nightly Build / build (push) Successful in 8m52s
Nightly Build / build (push) Successful in 8m52s
Each row of the project Files tab gains a download action, and the toolbar gains a Download ZIP button scoped to the folder being browsed (at the root, the whole project). Visible to read-only members too: download is a read. Single files need no new backend: they reuse GET /api/file?force_download. Directories go through the new GET /api/file/download, which builds the ZIP on the fly: an async task walks the tree and async_zip (Astral's maintained rs-async-zip fork) streams entries into a bounded duplex stream backing the response body — no temp file, no whole-archive buffer, backpressure for free, and the task dies with the client. Compression is per entry: Deflate at maximum level, except files whose magic bytes name an already-compressed format (media/PDF via the shared sniffer, the ZIP family, gzip/zstd/7z/rar, compressed audio), which are Stored. Entries are prefixed with the folder name, empty folders and unix permission bits survive, symlinks are never followed into the archive, and containment stays fail-closed under the resolved root. Covered by a round-trip test read back with the crate's own reader (and verified against unzip/python's zipfile).
This commit is contained in:
+7
-1
@@ -42,8 +42,14 @@ skald-core = { path = "crates/skald-core" }
|
||||
|
||||
axum = { version = "0.8", features = ["ws", "multipart"] }
|
||||
tokio = { version = "1.52.3", features = ["full"] }
|
||||
tokio-util = { version = "0.7", features = ["rt"] }
|
||||
tokio-util = { version = "0.7", features = ["rt", "io"] }
|
||||
futures = "0.3"
|
||||
# Streaming ZIP for directory downloads (src/frontend/api/files.rs): an async
|
||||
# ZIP writer over a duplex stream, so archives are built on the fly straight
|
||||
# into the HTTP body — no temp file, no whole-archive buffer. Astral's
|
||||
# maintained fork of rs-async-zip (used by uv); the `zip` crate has no
|
||||
# non-seekable writer in any non-yanked release.
|
||||
astral_async_zip = { version = "0.0.20", default-features = false, features = ["tokio", "deflate"] }
|
||||
tower-http = { version = "0.7.0", features = ["fs", "compression-gzip", "compression-br", "set-header"] }
|
||||
tower = "0.5"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
Reference in New Issue
Block a user