diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/transfers.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/transfers.js | 90 |
1 files changed, 72 insertions, 18 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transfers.js b/packages/meshbay-hub/src/meshbay_hub/static/transfers.js index 46f75a2..d3b164d 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/transfers.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/transfers.js @@ -22,6 +22,12 @@ const SPEED_WINDOW_MS = 5000; +/** Not finished: still preparing, waiting for a slot, or transferring. One + * definition, because six places ask and they were drifting apart. */ +function _live(status) { + return status === 'preparing' || status === 'queued' || status === 'running'; +} + function _abortError() { const err = new Error('Cancelled'); err.name = 'AbortError'; @@ -84,8 +90,7 @@ export class TransferStore { /** Running or waiting for a slot — what the nav badge counts. */ get pending() { - return this._items.filter( - it => it.status === 'running' || it.status === 'queued').length; + return this._items.filter(it => _live(it.status)).length; } _speed(it) { @@ -104,8 +109,28 @@ export class TransferStore { * `run` receives `{ signal, onProgress }`. It must poll `signal.aborted` — a * cancel that only sets a flag nobody reads is a button that lies. */ + /** + * Start a transfer. + * + * `run` receives `{ signal, onProgress, lease }`. It must poll + * `signal.aborted` — a cancel that only sets a flag nobody reads is a button + * that lies. + * + * `prepare` is optional and runs before anything else, with the row already + * on screen. It is where a download opens its target, which can take tens of + * seconds — the streamed path waits for the worker, twice, and a Save As + * dialog waits for a person. Doing that *before* creating the row meant three + * clicks produced no panel at all, not even the icon, and then several rows + * at once. Returning `false` drops the row again, which is what a dismissed + * dialog should look like: nothing, rather than a cancelled transfer nobody + * started. + * + * `makeLease` is called after `prepare` succeeds, never before. A granted + * slot must be taken up within the node's deadline, so it is asked for once + * there is somewhere to write — see file-utils.js's downloadEntry. + */ start({ kind, name, total = 0, transport = null, run, open = null, - lease = null }) { + lease = null, prepare = null, makeLease = null }) { const item = { id: _nextId++, kind, name, total, transport, open, lease, @@ -114,7 +139,8 @@ export class TransferStore { // 'running'. Two different things are true of it — nothing is moving, and // nothing is wrong — and a status that conflates them is what makes a // queue look like a hang. - status: lease && lease.state !== 'granted' ? 'queued' : 'running', + status: prepare ? 'preparing' + : (lease && lease.state !== 'granted' ? 'queued' : 'running'), ahead: (lease && lease.ahead) || 0, error: '', samples: [{ t: this._now(), done: 0 }], @@ -155,17 +181,32 @@ export class TransferStore { // appear to move — the widget showing "3 ahead" for ever while the node // quietly worked through the queue. Nothing about that looks wrong from // either side, which is why it needs a test rather than a reading. - if (item.lease) { - item.lease._onState = (lease) => { - if (item.status !== 'queued' && item.status !== 'running') return; - item.ahead = lease.ahead; - item.status = lease.state === 'granted' ? 'running' : 'queued'; - this._emit(); - }; - } + if (item.lease) this._watchLease(item); const promise = Promise.resolve() .then(async () => { + if (prepare) { + const ready = await prepare(); + if (item.signal.aborted) throw _abortError(); + if (ready === false) { + // Dismissed. Not a failure and not a cancellation: nothing was ever + // started, so nothing should be left on screen to explain. + this._drop(item.id); + return undefined; + } + if (ready && ready.name) item.name = ready.name; + item.status = 'running'; + this._emit(); + } + if (makeLease && !item.lease) { + item.lease = makeLease(); + this._watchLease(item); + if (item.lease.state !== 'granted') { + item.status = 'queued'; + item.ahead = item.lease.ahead || 0; + this._emit(); + } + } if (item.lease) { await item.lease.acquire(); if (item.signal.aborted) throw _abortError(); @@ -191,6 +232,21 @@ export class TransferStore { return item.id; } + _watchLease(item) { + item.lease._onState = (lease) => { + if (item.status !== 'queued' && item.status !== 'running') return; + item.ahead = lease.ahead; + item.status = lease.state === 'granted' ? 'running' : 'queued'; + this._emit(); + }; + } + + /** Remove a row entirely. Only for a transfer that never started. */ + _drop(id) { + this._items = this._items.filter(it => it.id !== id); + this._emit(); + } + /** * Hand a finished download to the browser to display. * @@ -209,7 +265,7 @@ export class TransferStore { // 'queued' too: a transfer waiting for a slot is exactly the one somebody // is most likely to give up on, and its queue entry has to go with it or // the node grants a slot to a transfer that will never use it. - if (!item || (item.status !== 'running' && item.status !== 'queued')) return; + if (!item || !_live(item.status)) return; item.signal.aborted = true; if (item.lease) item.lease.release('cancelled'); // Marked at once. The work stops when it next looks, but a cancelled @@ -221,14 +277,13 @@ export class TransferStore { cancelAll() { for (const it of this._items) { - if (it.status === 'running' || it.status === 'queued') this.cancel(it.id); + if (_live(it.status)) this.cancel(it.id); } } /** Drop everything finished, keeping what is still running or waiting. */ clearFinished() { - this._items = this._items.filter( - it => it.status === 'running' || it.status === 'queued'); + this._items = this._items.filter(it => _live(it.status)); this._emit(); } @@ -237,8 +292,7 @@ export class TransferStore { // slot can never be granted one, and the transfer would sit at "waiting" // for ever with nothing left to answer it. return this._items.some( - it => it.transport === transport - && (it.status === 'running' || it.status === 'queued')); + it => it.transport === transport && _live(it.status)); } /** |