diff options
Diffstat (limited to 'packages')
12 files changed, 132 insertions, 2 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index 0394af6..de47982 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -159,10 +159,20 @@ function TransferWidget() { const running = items.filter(i => i.status === 'running'); const waiting = items.filter( i => i.status === 'queued' || i.status === 'preparing'); + // Its own group, and not a leftover. + // + // "Finished" used to be defined as everything that is not running, queued or + // preparing — a definition by exclusion, which quietly swallowed `paused` the + // day pausing shipped. A transfer somebody stopped on purpose then sat under + // "Finished", beside the ones that are actually over, offering a resume + // button in the section of things that cannot be resumed. + const paused = items.filter(i => i.status === 'paused'); const finished = items.filter( i => i.status !== 'running' && i.status !== 'queued' - && i.status !== 'preparing'); - const active = running.length + waiting.length; + && i.status !== 'preparing' && i.status !== 'paused'); + // Paused counts as active: it is not over, the person means to come back to + // it, and the badge saying nothing is happening would be a lie. + const active = running.length + waiting.length + paused.length; // Grouped, and in this order: what is moving, what is waiting, what is over. // Re-sorting the flat list on every emit made rows jump under the pointer @@ -171,6 +181,7 @@ function TransferWidget() { const groups = [ ['running', running], ['waiting', waiting], + ['paused', paused], ['finished', finished], ].filter(([, rows]) => rows.length); diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js index fd63ddf..738b131 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js @@ -584,6 +584,7 @@ export default { 'transfers.summary': '{running} laufend · {waiting} wartend', 'transfers.group_running': 'Laufend', 'transfers.group_waiting': 'Wartend', + 'transfers.group_paused': 'Angehalten', 'transfers.group_finished': 'Abgeschlossen', 'transfers.cancel_one': '{name} abbrechen', 'transfers.pause': 'Anhalten', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js index e9d4072..0b9de8a 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js @@ -700,6 +700,7 @@ export default { 'transfers.summary': '{running} running · {waiting} waiting', 'transfers.group_running': 'Running', 'transfers.group_waiting': 'Waiting', + 'transfers.group_paused': 'Paused', 'transfers.group_finished': 'Finished', 'transfers.cancel_one': 'Cancel {name}', 'transfers.pause': 'Pause', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js index 4fa19e7..8da8f9f 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js @@ -580,6 +580,7 @@ export default { 'transfers.summary': '{running} en curso · {waiting} en espera', 'transfers.group_running': 'En curso', 'transfers.group_waiting': 'En espera', + 'transfers.group_paused': 'En pausa', 'transfers.group_finished': 'Finalizados', 'transfers.cancel_one': 'Cancelar {name}', 'transfers.pause': 'Pausar', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js index 6fba91d..3135c03 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js @@ -583,6 +583,7 @@ export default { 'transfers.summary': '{running} en cours · {waiting} en attente', 'transfers.group_running': 'En cours', 'transfers.group_waiting': 'En attente', + 'transfers.group_paused': 'En pause', 'transfers.group_finished': 'Terminés', 'transfers.cancel_one': 'Annuler {name}', 'transfers.pause': 'Suspendre', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js index 76b3101..91f0e9b 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js @@ -583,6 +583,7 @@ export default { 'transfers.summary': '{running} in corso · {waiting} in attesa', 'transfers.group_running': 'In corso', 'transfers.group_waiting': 'In attesa', + 'transfers.group_paused': 'In pausa', 'transfers.group_finished': 'Completati', 'transfers.cancel_one': 'Annulla {name}', 'transfers.pause': 'Sospendi', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js index cfc1125..b352188 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js @@ -575,6 +575,7 @@ export default { 'transfers.summary': '実行中 {running} · 待機中 {waiting}', 'transfers.group_running': '実行中', 'transfers.group_waiting': '待機中', + 'transfers.group_paused': '一時停止中', 'transfers.group_finished': '完了', 'transfers.cancel_one': '{name} をキャンセル', 'transfers.pause': '一時停止', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js index 29cc566..e0a799d 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js @@ -584,6 +584,7 @@ export default { 'transfers.summary': '{running} bezig · {waiting} wachtend', 'transfers.group_running': 'Bezig', 'transfers.group_waiting': 'Wachtend', + 'transfers.group_paused': 'Gepauzeerd', 'transfers.group_finished': 'Voltooid', 'transfers.cancel_one': '{name} annuleren', 'transfers.pause': 'Pauzeren', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js index 38fe714..e6319b0 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js @@ -596,6 +596,7 @@ export default { 'transfers.summary': '{running} w toku · {waiting} oczekuje', 'transfers.group_running': 'W toku', 'transfers.group_waiting': 'Oczekuje', + 'transfers.group_paused': 'Wstrzymane', 'transfers.group_finished': 'Zakończone', 'transfers.cancel_one': 'Anuluj {name}', 'transfers.pause': 'Wstrzymaj', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js index 5942b77..02c8356 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js @@ -582,6 +582,7 @@ export default { 'transfers.summary': '{running} em andamento · {waiting} aguardando', 'transfers.group_running': 'Em andamento', 'transfers.group_waiting': 'Aguardando', + 'transfers.group_paused': 'Pausados', 'transfers.group_finished': 'Concluídos', 'transfers.cancel_one': 'Cancelar {name}', 'transfers.pause': 'Pausar', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js index 56af6c8..6c4c73f 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js @@ -563,6 +563,7 @@ export default { 'transfers.summary': '进行中 {running} · 等待中 {waiting}', 'transfers.group_running': '进行中', 'transfers.group_waiting': '等待中', + 'transfers.group_paused': '已暂停', 'transfers.group_finished': '已完成', 'transfers.cancel_one': '取消 {name}', 'transfers.pause': '暂停', diff --git a/packages/meshbay-hub/tests/test_transfers.py b/packages/meshbay-hub/tests/test_transfers.py index f27d4fd..d19a458 100644 --- a/packages/meshbay-hub/tests/test_transfers.py +++ b/packages/meshbay-hub/tests/test_transfers.py @@ -770,3 +770,112 @@ def test_an_upload_handed_a_lease_it_cannot_recreate_is_not_offered_pause(tmp_pa t.cancel(id); """, tmp_path) assert out == ["status:running"] + + +def test_pausing_one_transfer_leaves_the_others_alone(tmp_path): + """Reported: three downloads running, one upload paused, and the three + downloads lost their pause buttons. + + The button is drawn from `pausable` and the status, so this asks the store + what it says about the other three at the moment one of them pauses. + """ + out = _run(_lease_stub() + """ + const t = new TransferStore(); + const leases = []; + const mk = (kind, name) => t.start({ + kind, name, total: 100, pausable: true, + makeLease: () => { const l = new L(); leases.push(l); return l; }, + run: async ({ signal, from }) => { + for (let i = from || 0; i < 40; i++) { + await new Promise(r => setTimeout(r, 5)); + if (signal.aborted) { const e = new Error('c'); e.name = 'AbortError'; throw e; } + if (signal.paused) { + signal.resumeFrom = i; + const e = new Error('p'); e.name = 'PausedError'; throw e; + } + } + }, + }); + mk('download', 'd1'); mk('download', 'd2'); mk('download', 'd3'); + mk('upload', 'u1'); + await new Promise(r => setTimeout(r, 5)); + for (const l of leases) l.grant(); + await new Promise(r => setTimeout(r, 20)); + const up = t.list().find(i => i.kind === 'upload'); + say('before:' + t.list().filter( + i => i.kind === 'download' && i.pausable && i.status === 'running').length); + t.pause(up.id); + await new Promise(r => setTimeout(r, 40)); + const rows = t.list(); + say('after:' + rows.filter( + i => i.kind === 'download' && i.pausable && i.status === 'running').length); + say('statuses:' + rows.map(i => i.kind[0] + ':' + i.status).join(',')); + for (const r of rows) t.cancel(r.id); + """, tmp_path) + assert out[0] == "before:3" + assert out[1] == "after:3", ( + f"pausing the upload changed the downloads — {out[2]}") + + +def test_a_paused_transfer_is_not_filed_under_finished(tmp_path): + """"Finished" was defined by exclusion — everything that is not running, + queued or preparing — so it quietly swallowed `paused` the day pausing + shipped. A transfer somebody stopped on purpose then sat beside the ones + that are actually over, offering a resume button in the section of things + that cannot be resumed. + + The three filters are lifted out of `app.js` and run, rather than described + here: a copy of them in this file would agree with a broken version by + construction. + """ + src = (STATIC / "app.js").read_text() + start = src.index(" const running = items.filter(") + block = src[start:src.index("const active =", start)] + + script = tmp_path / "groups.mjs" + script.write_text(""" +const items = [ + { id: 1, status: 'running' }, + { id: 2, status: 'queued' }, + { id: 3, status: 'preparing' }, + { id: 4, status: 'paused' }, + { id: 5, status: 'done' }, + { id: 6, status: 'failed' }, + { id: 7, status: 'cancelled' }, +]; +""" + block + """ +const seen = { running, waiting, paused, finished }; +console.log(JSON.stringify(Object.fromEntries( + Object.entries(seen).map(([k, v]) => [k, v.map(i => i.id)])))); +""") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True) + assert proc.returncode == 0, proc.stderr + groups = json.loads(proc.stdout) + + assert groups["paused"] == [4] + assert groups["finished"] == [5, 6, 7], ( + f"paused landed in {groups['finished']}") + assert groups["running"] == [1] and groups["waiting"] == [2, 3] + # Every row appears exactly once: a state added later that lands in no group + # is a transfer the panel simply does not show. + placed = sum((groups[k] for k in groups), []) + assert sorted(placed) == [1, 2, 3, 4, 5, 6, 7] + + +def test_a_paused_transfer_still_counts_as_active(tmp_path): + """The badge says how much is going on. A paused transfer is not over — the + person means to come back to it — so counting it as nothing would be a + panel that says "0" over work that is still there.""" + src = (STATIC / "app.js").read_text() + start = src.index(" const running = items.filter(") + block = src[start:src.index("\n\n", src.index("const active =", start))] + + script = tmp_path / "active.mjs" + script.write_text(""" +const items = [{ id: 1, status: 'paused' }, { id: 2, status: 'done' }]; +""" + block + """ +console.log(JSON.stringify({ active })); +""") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True) + assert proc.returncode == 0, proc.stderr + assert json.loads(proc.stdout)["active"] == 1 |