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 | 19 |
1 files changed, 14 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 bdfba6e..255c162 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js @@ -3,6 +3,7 @@ import * as platform from './platform.js'; import { t } from './i18n.js'; import { ask } from './ask.js'; import { ZipStream, entriesUnder } from './zipstream.js'; +import { portableName, portablePath } from './portable-name.js'; const FILE_ICONS = { video: '\u{1F3AC}', audio: '\u{1F3B5}', image: '\u{1F5BC}', @@ -428,6 +429,9 @@ async function pipelinedDownload(transport, gekKey, fileId, totalChunks, onChunk */ async function downloadEntry(transfers, transport, gek, entry) { const totalChunks = Math.ceil(entry.size / CHUNK_SIZE); + // Saved under a name every platform can write, and the row says so when that + // is not the node's (portable-name.js, docs/MESHBAY_DESIGN.md §10). + const saveName = portableName(entry.name); const openRef = { url: null }; let target = null; // The in-memory fallback's accumulator, held out here so a pause does not @@ -435,14 +439,15 @@ async function downloadEntry(transfers, transport, gek, entry) { const memoryChunks = new Array(totalChunks); transfers.start({ - kind: 'download', name: entry.name, total: entry.size, transport, + kind: 'download', name: saveName, total: entry.size, transport, + note: saveName !== entry.name ? t('transfers.renamed', { name: entry.name }) : '', // 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 _openTargetInTurn(entry.name, entry.size); + target = await _openTargetInTurn(saveName, entry.size); // Dismissed: nothing was started, so nothing is left on screen. if (target === false) return false; // `pausable` travels with the target, because only the target knows. The @@ -489,7 +494,7 @@ async function downloadEntry(transfers, transport, gek, entry) { transport, gek, entry.id, totalChunks, onChunk, null, signal, lease && lease.tr, from, memoryChunks); const blob = new Blob(chunks); - _saveBlob(blob, entry.name); + _saveBlob(blob, saveName); openRef.url = URL.createObjectURL(blob); } }, @@ -523,7 +528,10 @@ async function downloadDirectory(transfers, transport, gek, entries, dir, { setE return; } const totalBytes = files.reduce((n, f) => n + (f.entry.size || 0), 0); - const suggested = (dir.split('/').pop() || 'files') + '.zip'; + const suggested = portableName(dir.split('/').pop() || 'files') + '.zip'; + // Every name in the archive is made writable everywhere, or a Windows + // extraction refuses it; the row says how many changed. + const renamed = files.filter(f => portablePath(f.name) !== f.name).length; // Checked here rather than by disabling the button: Files zips a whole // multi-directory selection in one click (`for (const d of selectedDirs)`), @@ -548,6 +556,7 @@ async function downloadDirectory(transfers, transport, gek, entries, dir, { setE transfers.start({ kind: 'download', name: suggested, total: totalBytes, transport, + note: renamed ? t('transfers.renamed_n', { n: renamed }) : '', // 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. @@ -584,7 +593,7 @@ async function downloadDirectory(transfers, transport, gek, entries, dir, { setE }); for (const { entry, name } of files) { - await zip.begin(name, entry.size, + await zip.begin(portablePath(name), entry.size, new Date((entry.added_at || 0) * 1000)); // A zero-byte file has no chunk to ask for; the header and an empty // descriptor are the whole entry. |