summaryrefslogtreecommitdiffstats
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.js111
1 files changed, 48 insertions, 63 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 61002d6..3475f81 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js
@@ -321,47 +321,34 @@ async function pipelinedDownload(transport, gekKey, fileId, totalChunks, onChunk
* download button — both just want "get this entry to disk".
*/
async function downloadEntry(transfers, transport, gek, entry) {
- // The target FIRST, then the slot — and that order is load-bearing.
- //
- // Asking for the slot first looks better (the widget could draw a row while
- // the target is being chosen) and is wrong: a granted slot has to be taken up
- // within the node's acceptance deadline, and opening a target can take thirty
- // seconds of streamed-download timeouts, or as long as somebody leaves a Save
- // As dialog open. The node then revokes the grant and passes it to the next
- // in the queue — `transfer: reclaimed … (not_taken_up)` in its log — and this
- // download starts fetching under a `tr` that is no longer granted.
- //
- // Measured, not reasoned: three downloads started, one arrived, and the
- // node's log named the reason. Do not move this again without moving the
- // deadline, and the deadline exists so a client that dies between asking and
- // starting does not hold a slot nobody can use.
- let target;
- try {
- target = await _openDownloadTarget(entry.name, entry.size);
- } catch (err) {
- // The refusal belongs in the transfers panel, not in a console nobody
- // opens: that is where someone who just clicked Download is looking, and a
- // failed row naming the reason is the whole point of refusing rather than
- // filling the tab. Started only to be failed, deliberately.
- transfers.start({
- kind: 'download', name: entry.name, total: entry.size, transport,
- run: async () => { throw err; },
- });
- return;
- }
- if (target === false) return; // the picker was dismissed
-
- const openRef = { url: null };
const totalChunks = Math.ceil(entry.size / CHUNK_SIZE);
+ const openRef = { url: null };
+ let target = null;
+
transfers.start({
- kind: 'download', name: (target && target.name) || entry.name,
- total: entry.size, transport,
- // Asked for here, once there is somewhere to write: see the note above.
- lease: transport.openTransfer({
+ kind: 'download', name: entry.name, total: entry.size, transport,
+
+ // The row exists from the click. Opening a target is what takes the time —
+ // the streamed path waits for the worker (twice), a Save As dialog waits
+ // for a person — and doing it before the row meant three clicks produced no
+ // panel at all and then several rows at once.
+ prepare: async () => {
+ target = await _openDownloadTarget(entry.name, entry.size);
+ // Dismissed: nothing was started, so nothing is left on screen.
+ if (target === false) return false;
+ return target ? { name: target.name } : true;
+ },
+
+ // After the target, never before: a granted slot has to be taken up within
+ // the node's deadline, and opening a target can outlast it. See §8.1 of
+ // ~/next/improve-downloads.md — the other order was tried and cost two of
+ // three downloads.
+ makeLease: () => transport.openTransfer({
kind: 'download', bytes: entry.size, chunks: totalChunks }),
- open: target
- ? (target.open || null)
- : () => { if (openRef.url) window.open(openRef.url, '_blank'); },
+
+ open: () => (target && target.open) ? target.open()
+ : (openRef.url ? window.open(openRef.url, '_blank') : undefined),
+
run: async ({ signal, onProgress, lease }) => {
let done = 0;
const onChunk = (bytes) => { done += bytes; onProgress(done, entry.size); };
@@ -435,38 +422,36 @@ async function downloadDirectory(transfers, transport, gek, entries, dir, { setE
// 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.
- let target;
- try {
- target = await _openDownloadTarget(suggested, totalBytes, {
- types: [{ description: 'ZIP archive',
- accept: { 'application/zip': ['.zip'] } }],
- }, 0);
- } catch (err) {
- // Reported beside the folder that was clicked, like zip_too_large just
- // above — this function is called in a loop over a selection, and the
- // sibling folders must still download.
- setError(err.message);
- return;
- }
- if (target === false) return;
- if (!target && !confirm(t('group.zip_no_stream', {
- size: formatSize(totalBytes), name: suggested,
- }))) {
- return;
- }
const zipOpenRef = { url: null };
+ let target = null;
transfers.start({
- kind: 'download', name: (target && target.name) || suggested,
- total: totalBytes, transport,
+ kind: 'download', name: suggested, total: totalBytes, transport,
+
+ // Same order as downloadEntry: the row first, then the target, then the
+ // slot. A folder of forty files is exactly where the wait is longest.
+ prepare: async () => {
+ target = await _openDownloadTarget(suggested, totalBytes, {
+ types: [{ description: 'ZIP archive',
+ accept: { 'application/zip': ['.zip'] } }],
+ }, 0);
+ if (target === false) return false;
+ if (!target && !confirm(t('group.zip_no_stream', {
+ size: formatSize(totalBytes), name: suggested,
+ }))) {
+ return false;
+ }
+ return target ? { name: target.name } : true;
+ },
+
// **One** lease for the archive, not one per file. Dozens of leases for a
// folder would deadlock against the member's own cap: the job cannot finish
// until it holds them all, and it can never hold more than two.
- lease: transport.openTransfer({
+ makeLease: () => transport.openTransfer({
kind: 'download', bytes: totalBytes, chunks: files.length }),
- open: target
- ? (target.open || null)
- : () => { if (zipOpenRef.url) window.open(zipOpenRef.url, '_blank'); },
+
+ open: () => (target && target.open) ? target.open()
+ : (zipOpenRef.url ? window.open(zipOpenRef.url, '_blank') : undefined),
run: async ({ signal, onProgress, lease }) => {
const writable = target ? target.writable : null;
const parts = writable ? null : [];