aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/file-utils.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/file-utils.js33
1 files changed, 28 insertions, 5 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js b/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js
index 09ee6c9..241d761 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js
@@ -15,6 +15,14 @@ function formatSize(bytes) {
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
}
+// An arbitrary ceiling on one directory zip. Not a technical limit — the
+// writer streams and holds one chunk plus a record per file, so it would
+// happily produce a hundred gigabytes — but a deliberate one: past this
+// size the honest answer is a subfolder at a time, or the files
+// individually. Counted in the same 1024-based units formatSize prints, so
+// the number in the refusal is the number in this constant.
+const ZIP_MAX_BYTES = 512 * 1024 * 1024;
+
function formatDate(ts) {
return new Date(ts * 1000).toLocaleDateString(undefined, {
year: 'numeric', month: 'short', day: 'numeric',
@@ -240,10 +248,10 @@ async function downloadEntry(transfers, transport, gek, entry) {
/**
* Download a directory as a zip, written straight to disk.
*
- * An archive of a group directory is routinely tens of gigabytes, so it is
- * never held anywhere: each file is fetched chunk by chunk, decrypted, and
- * handed to the zip writer, which hands it to the file the browser opened.
- * Peak memory is one chunk plus one small record per file.
+ * Nothing is held anywhere: each file is fetched chunk by chunk, decrypted,
+ * and handed to the zip writer, which hands it to the file the browser
+ * opened. Peak memory is one chunk plus one small record per file — which is
+ * why ZIP_MAX_BYTES below is a policy, not a constraint this code has.
*
* Without the File System Access API there is nowhere to stream to, and the
* only alternative is to build the whole thing in memory — so that path is
@@ -266,6 +274,21 @@ async function downloadDirectory(transfers, transport, gek, entries, dir, { setE
const totalBytes = files.reduce((n, f) => n + (f.entry.size || 0), 0);
const suggested = (dir.split('/').pop() || 'files') + '.zip';
+ // Checked here rather than by disabling the button: Files zips a whole
+ // multi-directory selection in one click (`for (const d of selectedDirs)`),
+ // so the answer is per directory and has to be given where each one is
+ // actually started — an oversized folder is refused and its siblings still
+ // download. Before _openDownloadTarget, so nothing opens a save dialog for
+ // an archive that is not going to be written.
+ if (totalBytes > ZIP_MAX_BYTES) {
+ setError(t('group.zip_too_large', {
+ name: dir.split('/').pop() || dir,
+ size: formatSize(totalBytes),
+ limit: formatSize(ZIP_MAX_BYTES),
+ }));
+ return;
+ }
+
// totalBytes decides how this is delivered, but it is not the archive's
// size — headers and the central directory come on top — so it is not
// announced as a Content-Length that the download would then miss.
@@ -327,7 +350,7 @@ async function downloadDirectory(transfers, transport, gek, entries, dir, { setE
export {
FILE_ICONS,
- formatSize, formatDate, PREVIEWABLE_TEXT, canPreview, CHUNK_SIZE,
+ formatSize, formatDate, PREVIEWABLE_TEXT, canPreview, CHUNK_SIZE, ZIP_MAX_BYTES,
_openDownloadTarget, _saveBlob, pipelinedDownload, downloadEntry,
downloadDirectory,
};