Add in-browser zip download for small dandisets - #2855
Conversation
Requiring the DANDI CLI to download a dandiset is overkill for small datasets. This adds a "Download .zip" button to the dandiset download menu that fetches each asset's blob directly from S3 (CORS already allows it) and zips it client-side with client-zip — no backend changes and no server bandwidth cost, preserving the existing S3-direct model. - Gated to "small" versions (<= 2 GiB and <= 1000 files) since the archive is buffered in memory before saving. - Files nest under a single root folder matching the zip name. - Byte-level progress bar and a cancel button (AbortController). - Zarr assets are skipped (not single blobs) with a user-facing note. Prototype — not yet validated end-to-end in a running browser. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
download_button_demo.mov |
|
@yarikoptic , what do you think? Is this useful? |
|
this reminded me about and see there |
|
In-browser zip download eligibility The branch gates the feature on: has data (size > 0), size ≤ 2 GiB, and asset_count ≤ 1000.
|
|
@yarikoptic @CodyCBakerPhD, summarizing our discussion from Monday's call, plus my response to the jsdownloader idea. Where we landed on the call:
On the library question: I'd argue the generic library already exists: it's Middle path I'd propose: I'll refactor the generic part into a composable inside this repo, roughly On the threshold complaint-magnet concern: when a dandiset is over the limit, nothing is lost. The CLI instructions still show, exactly as today. The button is purely additive, and we can tune messaging (e.g., "too large for browser download, use the CLI") rather than architecture. If that plan sounds reasonable, I'll do the composable refactor in this PR and file a follow-up issue for the folder-level button and the streaming-to-disk cap raise. |
|
One known gap: the zip path uses a raw |
…n embargoed dandisets
The generic download logic (sequential fetch with byte-level progress,
AbortController wiring, client-zip integration, and blob save) now lives in
web/src/composables/useZipDownload.ts, taking a list of {name, url, size}
entries. DownloadDialog.vue keeps only the DANDI-specific parts: asset
pagination, Zarr filtering, eligibility gating, and the UI. A folder- or
subject-level download button can later feed the same composable a filtered
asset list.
The eligibility check now also requires embargo_status === 'OPEN'. The
composable fetches with a plain unauthenticated fetch, so embargoed assets
would fail with a 401; hiding the button is the conservative fix, and an
authenticated download path can follow up.
Also drop the incorrect `length` option previously passed to downloadZip:
client-zip expects a predicted byte length of the archive there, not a file
count. It was harmless since the response is buffered to a Blob, but it was
wrong.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
I've pushed the refactor described above. The generic logic now lives in a composable, Two other changes in the same commit:
Still to do: file the follow-up issue for the folder-level download button and for streaming to disk to raise the 2 GiB cap. |
Motivation
Requiring the DANDI CLI to download a dandiset is overkill for small datasets. The current Download menu only hands the user a
dandi download ...command — there's no way to grab a small dandiset straight from the browser.This adds a Download .zip button to the dandiset Download menu that bundles the dataset client-side and saves it through the normal browser download mechanism.
How it works
Access-Control-Allow-Origin: *, so browserfetchworks) and zips them client-side withclient-zip(~5 KB).000123-draft/sub-01/...).AbortControllerthat interrupts both the asset listing and in-flight fetches).Gating
Shown only for "small" versions: ≤ 2 GiB and ≤ 1000 files (constants at the top of the component). The archive is buffered in memory (
client-zip→Blob) before saving, which is why the cap exists.Video of sandbox testing below
Status / open questions
createWritable()) to lift the cap.🤖 Generated with Claude Code