diff options
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.js | 111 |
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 : []; |