From 798d09a9c5e6ac27a9c877aa108e07ef7324f4a2 Mon Sep 17 00:00:00 2001 From: Paris Theofanidis Date: Tue, 14 Jul 2026 13:21:13 +0300 Subject: [PATCH 1/3] Show Put.io transfer status details --- src/state/store.js | 13 ++++++++++--- src/transfer/service.js | 2 ++ src/web/downloads.js | 5 +++++ src/web/styles/07-downloads.css | 7 +++++++ test/download-metrics.test.js | 21 +++++++++++++++++++++ test/putio-client.test.js | 10 ++++++++++ test/state-store.test.js | 24 ++++++++++++++++++++++++ test/web-download-testids.test.js | 7 +++++++ 8 files changed, 86 insertions(+), 3 deletions(-) diff --git a/src/state/store.js b/src/state/store.js index f4d1eaa..b60828d 100644 --- a/src/state/store.js +++ b/src/state/store.js @@ -227,6 +227,7 @@ export class StateStore { download_dir TEXT NOT NULL DEFAULT '', lifecycle TEXT NOT NULL DEFAULT 'remote', putio_status TEXT NOT NULL DEFAULT 'UNKNOWN', + putio_status_message TEXT NOT NULL DEFAULT '', percent_done INTEGER NOT NULL DEFAULT 0, completion_percent INTEGER NOT NULL DEFAULT 0, total_size INTEGER NOT NULL DEFAULT 0, @@ -322,6 +323,7 @@ export class StateStore { this.ensureColumn('profiles', 'client_use_ssl', 'INTEGER NOT NULL DEFAULT 0'); this.ensureColumn('transfers', 'profile_id', 'INTEGER REFERENCES profiles(id) ON DELETE SET NULL'); this.ensureColumn('transfers', 'completion_percent', 'INTEGER NOT NULL DEFAULT 0'); + this.ensureColumn('transfers', 'putio_status_message', "TEXT NOT NULL DEFAULT ''"); this.ensureColumn('transfer_files', 'download_speed', 'INTEGER NOT NULL DEFAULT 0'); this.migrateTransferAssociations(); this.migrateMagnetTransferHashes(); @@ -739,11 +741,11 @@ export class StateStore { INSERT INTO transfers ( profile_id, putio_transfer_id, putio_file_id, save_parent_id, hash, name, source, source_type, category, download_dir, lifecycle, putio_status, - percent_done, completion_percent, total_size, downloaded_ever, uploaded_ever, + putio_status_message, percent_done, completion_percent, total_size, downloaded_ever, uploaded_ever, download_speed, upload_speed, eta, error, error_string, created_at, updated_at ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `).run( input.profile_id ?? null, input.putio_transfer_id ?? null, @@ -757,6 +759,7 @@ export class StateStore { input.download_dir ?? '', input.lifecycle ?? 'remote', input.putio_status ?? 'UNKNOWN', + input.putio_status_message ?? '', input.percent_done ?? 0, input.completion_percent ?? 0, input.total_size ?? input.size ?? 0, @@ -780,6 +783,7 @@ export class StateStore { source: input.source ?? remote.source, source_type: input.source_type ?? remote.source_type, putio_status: input.putio_status ?? remote.putio_status, + putio_status_message: input.putio_status_message ?? remote.putio_status_message, percent_done: input.percent_done ?? remote.percent_done, completion_percent: input.completion_percent ?? remote.completion_percent, total_size: input.total_size ?? input.size ?? remote.total_size, @@ -789,7 +793,7 @@ export class StateStore { this.db.prepare(` UPDATE transfers SET putio_transfer_id = ?, putio_file_id = ?, save_parent_id = ?, name = ?, - source = ?, source_type = ?, putio_status = ?, percent_done = ?, + source = ?, source_type = ?, putio_status = ?, putio_status_message = ?, percent_done = ?, completion_percent = ?, total_size = ?, uploaded_ever = ?, upload_speed = ?, updated_at = ? WHERE id = ? @@ -801,6 +805,7 @@ export class StateStore { merged.source, merged.source_type, merged.putio_status, + merged.putio_status_message, merged.percent_done, merged.completion_percent, merged.total_size, @@ -873,6 +878,7 @@ export class StateStore { 'save_parent_id', 'name', 'putio_status', + 'putio_status_message', 'percent_done', 'completion_percent', 'uploaded_ever', @@ -926,6 +932,7 @@ export class StateStore { a.download_dir, a.lifecycle, r.putio_status, + r.putio_status_message, r.percent_done, r.completion_percent, COALESCE(a.total_size, r.total_size) AS total_size, diff --git a/src/transfer/service.js b/src/transfer/service.js index 86e769a..f5654b6 100644 --- a/src/transfer/service.js +++ b/src/transfer/service.js @@ -79,6 +79,7 @@ function putioTransferToStoreInput(transfer, fallback = {}) { download_dir: fallback.download_dir, lifecycle, putio_status: transfer.status, + putio_status_message: transfer.statusMessage, percent_done: transfer.percentDone, completion_percent: transfer.completionPercent, total_size: transfer.size, @@ -775,6 +776,7 @@ export class TransferService { downloadAt: profile ? path.join(profile.download_at, row.category ?? '') : '', lifecycle: row.lifecycle, putioStatus: row.putio_status, + putioStatusMessage: row.putio_status_message, putioProgress: Math.max(0, Math.min(100, Number(row.percent_done ?? 0))), putioCompletion: Math.max(0, Math.min(100, Number(row.completion_percent ?? 0))), localProgress: Number(stats.total_size ?? 0) > 0 diff --git a/src/web/downloads.js b/src/web/downloads.js index 1ec99aa..60a4c75 100644 --- a/src/web/downloads.js +++ b/src/web/downloads.js @@ -95,6 +95,7 @@ export function createDownloadRow(download) {
${progressLine('Put.io', 0, 'putio-bar', 'putio-progress')} ${progressLine('Local', 0, 'local-bar', 'local-progress', 'local')} +
@@ -206,6 +207,10 @@ export function updateDownloadRow(row, download) { setText(startButton.querySelector('[data-role="start-label"]'), starting ? 'Starting' : 'Start'); setProgressValue(row, 'putio-bar', 'putio-progress', putioProgress); setProgressValue(row, 'local-bar', 'local-progress', localProgress); + const putioStatusMessage = String(download.putioStatusMessage ?? '').trim(); + const putioStatus = row.querySelector('[data-role="putio-status-message"]'); + setText(putioStatus, putioStatusMessage ? `Put.io status: ${putioStatusMessage}` : ''); + setHidden(putioStatus, !putioStatusMessage); populateFilePanel(row, download, fileItems); } diff --git a/src/web/styles/07-downloads.css b/src/web/styles/07-downloads.css index 99c165d..6445c43 100644 --- a/src/web/styles/07-downloads.css +++ b/src/web/styles/07-downloads.css @@ -277,6 +277,13 @@ gap: 9px; } +.putio-status-message { + margin-left: 54px; + color: var(--muted); + font-size: 11.5px; + overflow-wrap: anywhere; +} + .download-footer { display: flex; align-items: center; diff --git a/test/download-metrics.test.js b/test/download-metrics.test.js index 540c1cc..1638d93 100644 --- a/test/download-metrics.test.js +++ b/test/download-metrics.test.js @@ -114,6 +114,27 @@ test('local download progress updates dashboard speed and ETA metrics', async () } }); +test('put.io refresh exposes the transfer status message on the dashboard', async () => { + const harness = await createHarness([{ + id: 70, + fileId: 80, + saveParentId: 42, + hash: 'putiostatushash', + name: 'Waiting Release', + status: 'DOWNLOADING', + statusMessage: 'Waiting for torrent details from the network...', + percentDone: 0, + }]); + try { + await harness.service.refreshRemoteTransfers(); + + const [download] = harness.service.listDownloads(); + assert.equal(download.putioStatusMessage, 'Waiting for torrent details from the network...'); + } finally { + harness.store.close(); + } +}); + test('dashboard reports multi-file progress details', async () => { const harness = await createHarness(); try { diff --git a/test/putio-client.test.js b/test/putio-client.test.js index 14a085a..e068214 100644 --- a/test/putio-client.test.js +++ b/test/putio-client.test.js @@ -124,6 +124,16 @@ test('PutioClient normalizes transfer and file endpoints', async () => { assert.equal(calls.at(-1).url, 'https://api.put.io/v2/files/delete'); }); +test('normalizeTransfer keeps Put.io status details', () => { + const transfer = normalizeTransfer({ + id: 22, + status: 'DOWNLOADING', + status_message: 'Waiting for torrent details from the network...', + }); + + assert.equal(transfer.statusMessage, 'Waiting for torrent details from the network...'); +}); + test('PutioClient handles folder creation and transfer edge cases', async () => { const missingFolder = createFetch([ { body: { files: [] } }, diff --git a/test/state-store.test.js b/test/state-store.test.js index a162302..210955f 100644 --- a/test/state-store.test.js +++ b/test/state-store.test.js @@ -65,6 +65,30 @@ test('createOrUpdateTransfer persists put.io completion_percent across updates', } }); +test('createOrUpdateTransfer persists put.io status messages across updates', () => { + const store = new StateStore(':memory:'); + try { + const created = store.createOrUpdateTransfer({ + putio_transfer_id: 12, + hash: 'statusmessagehash', + name: 'Waiting Transfer', + putio_status: 'DOWNLOADING', + putio_status_message: 'Waiting for torrent details from the network...', + }); + assert.equal(created.putio_status_message, 'Waiting for torrent details from the network...'); + + const updated = store.createOrUpdateTransfer({ + putio_transfer_id: 12, + hash: 'statusmessagehash', + putio_status_message: 'No peers', + }); + assert.equal(updated.id, created.id); + assert.equal(updated.putio_status_message, 'No peers'); + } finally { + store.close(); + } +}); + test('profile rows migrate local_path to downloadAt', async () => { const root = await mkdtemp(path.join(tmpdir(), 'putiorr-store-')); const dbPath = path.join(root, 'state.sqlite'); diff --git a/test/web-download-testids.test.js b/test/web-download-testids.test.js index b810f55..516095d 100644 --- a/test/web-download-testids.test.js +++ b/test/web-download-testids.test.js @@ -25,3 +25,10 @@ test('downloads UI exposes stable data-testid hooks for frontend tests', () => { assert.match(source, new RegExp(`data-testid=["']${testId}["']|['"]data-testid['"], ['"]${testId}['"]`)); } }); + +test('downloads UI renders Put.io status details below the progress bars', () => { + const downloadsJs = readFileSync(new URL('../src/web/downloads.js', import.meta.url), 'utf8'); + + assert.match(downloadsJs, /data-role="putio-status-message"/); + assert.match(downloadsJs, /Put\.io status: \$\{putioStatusMessage\}/); +}); From 7eb34c2cb78d43c8a5a951d8bdc8524c89723759 Mon Sep 17 00:00:00 2001 From: Paris Theofanidis Date: Tue, 14 Jul 2026 13:28:33 +0300 Subject: [PATCH 2/3] Show Put.io peer availability details --- src/putio/client.js | 2 ++ src/state/store.js | 29 +++++++++++++++++++++++++---- src/transfer/service.js | 7 +++++++ src/web/downloads.js | 5 +++-- src/web/util.js | 11 +++++++++++ test/download-metrics.test.js | 10 ++++++++++ test/putio-client.test.js | 4 ++++ test/state-store.test.js | 15 ++++++++++++--- test/web-download-testids.test.js | 18 ++++++++++++++++-- 9 files changed, 90 insertions(+), 11 deletions(-) diff --git a/src/putio/client.js b/src/putio/client.js index b839335..3b3a08f 100644 --- a/src/putio/client.js +++ b/src/putio/client.js @@ -42,6 +42,8 @@ export function normalizeTransfer(transfer) { errorMessage: transfer.error_message ?? transfer.errorMessage ?? '', percentDone: Number(transfer.percent_done ?? transfer.percentDone ?? 0), completionPercent: Number(transfer.completion_percent ?? transfer.completionPercent ?? 0), + peers: Number(transfer.peers ?? 0), + availability: Number(transfer.availability ?? 0), size: Number(transfer.size ?? 0), downloaded: Number(transfer.downloaded ?? 0), uploaded: Number(transfer.uploaded ?? 0), diff --git a/src/state/store.js b/src/state/store.js index b60828d..c24accd 100644 --- a/src/state/store.js +++ b/src/state/store.js @@ -228,6 +228,8 @@ export class StateStore { lifecycle TEXT NOT NULL DEFAULT 'remote', putio_status TEXT NOT NULL DEFAULT 'UNKNOWN', putio_status_message TEXT NOT NULL DEFAULT '', + putio_peers INTEGER NOT NULL DEFAULT 0, + putio_availability INTEGER NOT NULL DEFAULT 0, percent_done INTEGER NOT NULL DEFAULT 0, completion_percent INTEGER NOT NULL DEFAULT 0, total_size INTEGER NOT NULL DEFAULT 0, @@ -324,6 +326,8 @@ export class StateStore { this.ensureColumn('transfers', 'profile_id', 'INTEGER REFERENCES profiles(id) ON DELETE SET NULL'); this.ensureColumn('transfers', 'completion_percent', 'INTEGER NOT NULL DEFAULT 0'); this.ensureColumn('transfers', 'putio_status_message', "TEXT NOT NULL DEFAULT ''"); + this.ensureColumn('transfers', 'putio_peers', 'INTEGER NOT NULL DEFAULT 0'); + this.ensureColumn('transfers', 'putio_availability', 'INTEGER NOT NULL DEFAULT 0'); this.ensureColumn('transfer_files', 'download_speed', 'INTEGER NOT NULL DEFAULT 0'); this.migrateTransferAssociations(); this.migrateMagnetTransferHashes(); @@ -741,11 +745,12 @@ export class StateStore { INSERT INTO transfers ( profile_id, putio_transfer_id, putio_file_id, save_parent_id, hash, name, source, source_type, category, download_dir, lifecycle, putio_status, - putio_status_message, percent_done, completion_percent, total_size, downloaded_ever, uploaded_ever, + putio_status_message, putio_peers, putio_availability, percent_done, completion_percent, + total_size, downloaded_ever, uploaded_ever, download_speed, upload_speed, eta, error, error_string, created_at, updated_at ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `).run( input.profile_id ?? null, input.putio_transfer_id ?? null, @@ -760,6 +765,8 @@ export class StateStore { input.lifecycle ?? 'remote', input.putio_status ?? 'UNKNOWN', input.putio_status_message ?? '', + input.putio_peers ?? 0, + input.putio_availability ?? 0, input.percent_done ?? 0, input.completion_percent ?? 0, input.total_size ?? input.size ?? 0, @@ -784,17 +791,21 @@ export class StateStore { source_type: input.source_type ?? remote.source_type, putio_status: input.putio_status ?? remote.putio_status, putio_status_message: input.putio_status_message ?? remote.putio_status_message, + putio_peers: input.putio_peers ?? remote.putio_peers, + putio_availability: input.putio_availability ?? remote.putio_availability, percent_done: input.percent_done ?? remote.percent_done, completion_percent: input.completion_percent ?? remote.completion_percent, total_size: input.total_size ?? input.size ?? remote.total_size, + downloaded_ever: input.downloaded_ever ?? remote.downloaded_ever, uploaded_ever: input.uploaded_ever ?? remote.uploaded_ever, upload_speed: input.upload_speed ?? remote.upload_speed, }; this.db.prepare(` UPDATE transfers SET putio_transfer_id = ?, putio_file_id = ?, save_parent_id = ?, name = ?, - source = ?, source_type = ?, putio_status = ?, putio_status_message = ?, percent_done = ?, - completion_percent = ?, total_size = ?, uploaded_ever = ?, upload_speed = ?, + source = ?, source_type = ?, putio_status = ?, putio_status_message = ?, + putio_peers = ?, putio_availability = ?, percent_done = ?, completion_percent = ?, + total_size = ?, downloaded_ever = ?, uploaded_ever = ?, upload_speed = ?, updated_at = ? WHERE id = ? `).run( @@ -806,9 +817,12 @@ export class StateStore { merged.source_type, merged.putio_status, merged.putio_status_message, + merged.putio_peers, + merged.putio_availability, merged.percent_done, merged.completion_percent, merged.total_size, + merged.downloaded_ever, merged.uploaded_ever, merged.upload_speed, timestamp, @@ -879,6 +893,8 @@ export class StateStore { 'name', 'putio_status', 'putio_status_message', + 'putio_peers', + 'putio_availability', 'percent_done', 'completion_percent', 'uploaded_ever', @@ -933,8 +949,13 @@ export class StateStore { a.lifecycle, r.putio_status, r.putio_status_message, + r.putio_peers, + r.putio_availability, r.percent_done, r.completion_percent, + r.total_size AS putio_total_size, + r.downloaded_ever AS putio_downloaded, + r.uploaded_ever AS putio_uploaded, COALESCE(a.total_size, r.total_size) AS total_size, a.downloaded_ever, r.uploaded_ever, diff --git a/src/transfer/service.js b/src/transfer/service.js index f5654b6..0159741 100644 --- a/src/transfer/service.js +++ b/src/transfer/service.js @@ -80,6 +80,8 @@ function putioTransferToStoreInput(transfer, fallback = {}) { lifecycle, putio_status: transfer.status, putio_status_message: transfer.statusMessage, + putio_peers: transfer.peers, + putio_availability: transfer.availability, percent_done: transfer.percentDone, completion_percent: transfer.completionPercent, total_size: transfer.size, @@ -777,6 +779,11 @@ export class TransferService { lifecycle: row.lifecycle, putioStatus: row.putio_status, putioStatusMessage: row.putio_status_message, + putioPeers: row.putio_peers, + putioAvailability: row.putio_availability, + putioDownloaded: row.putio_downloaded, + putioUploaded: row.putio_uploaded, + putioTotalSize: row.putio_total_size, putioProgress: Math.max(0, Math.min(100, Number(row.percent_done ?? 0))), putioCompletion: Math.max(0, Math.min(100, Number(row.completion_percent ?? 0))), localProgress: Number(stats.total_size ?? 0) > 0 diff --git a/src/web/downloads.js b/src/web/downloads.js index 60a4c75..dfddc39 100644 --- a/src/web/downloads.js +++ b/src/web/downloads.js @@ -12,6 +12,7 @@ import { import { clampPercent, formatBytes, + formatPutioStatusDetails, formatSpeed, formatEta, statusLabel, @@ -207,9 +208,9 @@ export function updateDownloadRow(row, download) { setText(startButton.querySelector('[data-role="start-label"]'), starting ? 'Starting' : 'Start'); setProgressValue(row, 'putio-bar', 'putio-progress', putioProgress); setProgressValue(row, 'local-bar', 'local-progress', localProgress); - const putioStatusMessage = String(download.putioStatusMessage ?? '').trim(); + const putioStatusMessage = formatPutioStatusDetails(download); const putioStatus = row.querySelector('[data-role="putio-status-message"]'); - setText(putioStatus, putioStatusMessage ? `Put.io status: ${putioStatusMessage}` : ''); + setText(putioStatus, putioStatusMessage); setHidden(putioStatus, !putioStatusMessage); populateFilePanel(row, download, fileItems); } diff --git a/src/web/util.js b/src/web/util.js index f166301..8949eb0 100644 --- a/src/web/util.js +++ b/src/web/util.js @@ -138,6 +138,17 @@ export function formatBytes(value) { return `${Math.round(bytes)} B`; } +export function formatPutioStatusDetails(download) { + const message = String(download.putioStatusMessage ?? '').trim(); + if (message) return message; + if (download.lifecycle !== 'remote' || download.putioStatus !== 'DOWNLOADING') return ''; + + const peers = Math.max(0, Number(download.putioPeers ?? 0)); + const peerText = peers > 0 ? `${peers} peer${peers === 1 ? '' : 's'}` : 'No peers'; + const totalSize = Number(download.putioTotalSize ?? 0); + return `${peerText} | downloaded: ${formatBytes(download.putioDownloaded)} of ${formatBytes(totalSize)} | uploaded: ${formatBytes(download.putioUploaded)} of ${formatBytes(totalSize)} | availability: ${clampPercent(download.putioAvailability)}%`; +} + export function formatWholeBytes(value) { const bytes = Number(value ?? 0); if (bytes <= 0) return '0 B'; diff --git a/test/download-metrics.test.js b/test/download-metrics.test.js index 1638d93..f824547 100644 --- a/test/download-metrics.test.js +++ b/test/download-metrics.test.js @@ -123,6 +123,11 @@ test('put.io refresh exposes the transfer status message on the dashboard', asyn name: 'Waiting Release', status: 'DOWNLOADING', statusMessage: 'Waiting for torrent details from the network...', + peers: 2, + availability: 11, + downloaded: 108_480_000, + uploaded: 0, + size: 931_980_000, percentDone: 0, }]); try { @@ -130,6 +135,11 @@ test('put.io refresh exposes the transfer status message on the dashboard', asyn const [download] = harness.service.listDownloads(); assert.equal(download.putioStatusMessage, 'Waiting for torrent details from the network...'); + assert.equal(download.putioPeers, 2); + assert.equal(download.putioAvailability, 11); + assert.equal(download.putioDownloaded, 108_480_000); + assert.equal(download.putioUploaded, 0); + assert.equal(download.putioTotalSize, 931_980_000); } finally { harness.store.close(); } diff --git a/test/putio-client.test.js b/test/putio-client.test.js index e068214..abc4ac0 100644 --- a/test/putio-client.test.js +++ b/test/putio-client.test.js @@ -129,9 +129,13 @@ test('normalizeTransfer keeps Put.io status details', () => { id: 22, status: 'DOWNLOADING', status_message: 'Waiting for torrent details from the network...', + peers: 2, + availability: 11, }); assert.equal(transfer.statusMessage, 'Waiting for torrent details from the network...'); + assert.equal(transfer.peers, 2); + assert.equal(transfer.availability, 11); }); test('PutioClient handles folder creation and transfer edge cases', async () => { diff --git a/test/state-store.test.js b/test/state-store.test.js index 210955f..7dd99d0 100644 --- a/test/state-store.test.js +++ b/test/state-store.test.js @@ -65,7 +65,7 @@ test('createOrUpdateTransfer persists put.io completion_percent across updates', } }); -test('createOrUpdateTransfer persists put.io status messages across updates', () => { +test('createOrUpdateTransfer persists put.io status details across updates', () => { const store = new StateStore(':memory:'); try { const created = store.createOrUpdateTransfer({ @@ -74,16 +74,25 @@ test('createOrUpdateTransfer persists put.io status messages across updates', () name: 'Waiting Transfer', putio_status: 'DOWNLOADING', putio_status_message: 'Waiting for torrent details from the network...', + putio_peers: 0, + putio_availability: 0, + downloaded_ever: 0, }); assert.equal(created.putio_status_message, 'Waiting for torrent details from the network...'); const updated = store.createOrUpdateTransfer({ putio_transfer_id: 12, hash: 'statusmessagehash', - putio_status_message: 'No peers', + putio_status_message: '', + putio_peers: 2, + putio_availability: 11, + downloaded_ever: 108_480_000, }); assert.equal(updated.id, created.id); - assert.equal(updated.putio_status_message, 'No peers'); + assert.equal(updated.putio_status_message, ''); + assert.equal(updated.putio_peers, 2); + assert.equal(updated.putio_availability, 11); + assert.equal(updated.putio_downloaded, 108_480_000); } finally { store.close(); } diff --git a/test/web-download-testids.test.js b/test/web-download-testids.test.js index 516095d..31e79e4 100644 --- a/test/web-download-testids.test.js +++ b/test/web-download-testids.test.js @@ -1,6 +1,7 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import { readFileSync } from 'node:fs'; +import { formatPutioStatusDetails } from '../src/web/util.js'; test('downloads UI exposes stable data-testid hooks for frontend tests', () => { const html = readFileSync(new URL('../src/web/index.html', import.meta.url), 'utf8'); @@ -26,9 +27,22 @@ test('downloads UI exposes stable data-testid hooks for frontend tests', () => { } }); -test('downloads UI renders Put.io status details below the progress bars', () => { +test('downloads UI renders Put.io status messages or transfer metrics below the progress bars', () => { const downloadsJs = readFileSync(new URL('../src/web/downloads.js', import.meta.url), 'utf8'); assert.match(downloadsJs, /data-role="putio-status-message"/); - assert.match(downloadsJs, /Put\.io status: \$\{putioStatusMessage\}/); + assert.equal(formatPutioStatusDetails({ + lifecycle: 'remote', + putioStatus: 'DOWNLOADING', + putioPeers: 2, + putioAvailability: 11, + putioDownloaded: 108_480_000, + putioUploaded: 0, + putioTotalSize: 931_980_000, + }), '2 peers | downloaded: 103.5 MB of 888.8 MB | uploaded: 0 B of 888.8 MB | availability: 11%'); + assert.equal(formatPutioStatusDetails({ + lifecycle: 'remote', + putioStatus: 'DOWNLOADING', + putioStatusMessage: 'Waiting for torrent details from the network...', + }), 'Waiting for torrent details from the network...'); }); From f5803b8d25c5a727fdab209a830126037b3c5c5d Mon Sep 17 00:00:00 2001 From: Paris Theofanidis Date: Tue, 14 Jul 2026 13:34:44 +0300 Subject: [PATCH 3/3] Limit Put.io details to peers and availability --- src/state/store.js | 7 +------ src/transfer/service.js | 3 --- src/web/downloads.js | 13 +++++++++++-- src/web/util.js | 11 ----------- test/download-metrics.test.js | 6 ------ test/state-store.test.js | 3 --- test/web-download-testids.test.js | 19 +++---------------- 7 files changed, 15 insertions(+), 47 deletions(-) diff --git a/src/state/store.js b/src/state/store.js index c24accd..13994c1 100644 --- a/src/state/store.js +++ b/src/state/store.js @@ -796,7 +796,6 @@ export class StateStore { percent_done: input.percent_done ?? remote.percent_done, completion_percent: input.completion_percent ?? remote.completion_percent, total_size: input.total_size ?? input.size ?? remote.total_size, - downloaded_ever: input.downloaded_ever ?? remote.downloaded_ever, uploaded_ever: input.uploaded_ever ?? remote.uploaded_ever, upload_speed: input.upload_speed ?? remote.upload_speed, }; @@ -805,7 +804,7 @@ export class StateStore { SET putio_transfer_id = ?, putio_file_id = ?, save_parent_id = ?, name = ?, source = ?, source_type = ?, putio_status = ?, putio_status_message = ?, putio_peers = ?, putio_availability = ?, percent_done = ?, completion_percent = ?, - total_size = ?, downloaded_ever = ?, uploaded_ever = ?, upload_speed = ?, + total_size = ?, uploaded_ever = ?, upload_speed = ?, updated_at = ? WHERE id = ? `).run( @@ -822,7 +821,6 @@ export class StateStore { merged.percent_done, merged.completion_percent, merged.total_size, - merged.downloaded_ever, merged.uploaded_ever, merged.upload_speed, timestamp, @@ -953,9 +951,6 @@ export class StateStore { r.putio_availability, r.percent_done, r.completion_percent, - r.total_size AS putio_total_size, - r.downloaded_ever AS putio_downloaded, - r.uploaded_ever AS putio_uploaded, COALESCE(a.total_size, r.total_size) AS total_size, a.downloaded_ever, r.uploaded_ever, diff --git a/src/transfer/service.js b/src/transfer/service.js index 0159741..96daafd 100644 --- a/src/transfer/service.js +++ b/src/transfer/service.js @@ -781,9 +781,6 @@ export class TransferService { putioStatusMessage: row.putio_status_message, putioPeers: row.putio_peers, putioAvailability: row.putio_availability, - putioDownloaded: row.putio_downloaded, - putioUploaded: row.putio_uploaded, - putioTotalSize: row.putio_total_size, putioProgress: Math.max(0, Math.min(100, Number(row.percent_done ?? 0))), putioCompletion: Math.max(0, Math.min(100, Number(row.completion_percent ?? 0))), localProgress: Number(stats.total_size ?? 0) > 0 diff --git a/src/web/downloads.js b/src/web/downloads.js index dfddc39..a5b8ea9 100644 --- a/src/web/downloads.js +++ b/src/web/downloads.js @@ -12,7 +12,6 @@ import { import { clampPercent, formatBytes, - formatPutioStatusDetails, formatSpeed, formatEta, statusLabel, @@ -208,7 +207,7 @@ export function updateDownloadRow(row, download) { setText(startButton.querySelector('[data-role="start-label"]'), starting ? 'Starting' : 'Start'); setProgressValue(row, 'putio-bar', 'putio-progress', putioProgress); setProgressValue(row, 'local-bar', 'local-progress', localProgress); - const putioStatusMessage = formatPutioStatusDetails(download); + const putioStatusMessage = putioStatusDetails(download); const putioStatus = row.querySelector('[data-role="putio-status-message"]'); setText(putioStatus, putioStatusMessage); setHidden(putioStatus, !putioStatusMessage); @@ -757,3 +756,13 @@ export function downloadStatusText(download) { } return `${phase} ยท ${clampPercent(download.putioProgress)}%`; } + +export function putioStatusDetails(download) { + const message = String(download.putioStatusMessage ?? '').trim(); + if (message) return message; + if (download.lifecycle !== 'remote' || download.putioStatus !== 'DOWNLOADING') return ''; + + const peers = Math.max(0, Number(download.putioPeers ?? 0)); + const peerText = peers > 0 ? `${peers} peer${peers === 1 ? '' : 's'}` : 'No peers'; + return `${peerText} | availability: ${clampPercent(download.putioAvailability)}%`; +} diff --git a/src/web/util.js b/src/web/util.js index 8949eb0..f166301 100644 --- a/src/web/util.js +++ b/src/web/util.js @@ -138,17 +138,6 @@ export function formatBytes(value) { return `${Math.round(bytes)} B`; } -export function formatPutioStatusDetails(download) { - const message = String(download.putioStatusMessage ?? '').trim(); - if (message) return message; - if (download.lifecycle !== 'remote' || download.putioStatus !== 'DOWNLOADING') return ''; - - const peers = Math.max(0, Number(download.putioPeers ?? 0)); - const peerText = peers > 0 ? `${peers} peer${peers === 1 ? '' : 's'}` : 'No peers'; - const totalSize = Number(download.putioTotalSize ?? 0); - return `${peerText} | downloaded: ${formatBytes(download.putioDownloaded)} of ${formatBytes(totalSize)} | uploaded: ${formatBytes(download.putioUploaded)} of ${formatBytes(totalSize)} | availability: ${clampPercent(download.putioAvailability)}%`; -} - export function formatWholeBytes(value) { const bytes = Number(value ?? 0); if (bytes <= 0) return '0 B'; diff --git a/test/download-metrics.test.js b/test/download-metrics.test.js index f824547..69f7bce 100644 --- a/test/download-metrics.test.js +++ b/test/download-metrics.test.js @@ -125,9 +125,6 @@ test('put.io refresh exposes the transfer status message on the dashboard', asyn statusMessage: 'Waiting for torrent details from the network...', peers: 2, availability: 11, - downloaded: 108_480_000, - uploaded: 0, - size: 931_980_000, percentDone: 0, }]); try { @@ -137,9 +134,6 @@ test('put.io refresh exposes the transfer status message on the dashboard', asyn assert.equal(download.putioStatusMessage, 'Waiting for torrent details from the network...'); assert.equal(download.putioPeers, 2); assert.equal(download.putioAvailability, 11); - assert.equal(download.putioDownloaded, 108_480_000); - assert.equal(download.putioUploaded, 0); - assert.equal(download.putioTotalSize, 931_980_000); } finally { harness.store.close(); } diff --git a/test/state-store.test.js b/test/state-store.test.js index 7dd99d0..af526f2 100644 --- a/test/state-store.test.js +++ b/test/state-store.test.js @@ -76,7 +76,6 @@ test('createOrUpdateTransfer persists put.io status details across updates', () putio_status_message: 'Waiting for torrent details from the network...', putio_peers: 0, putio_availability: 0, - downloaded_ever: 0, }); assert.equal(created.putio_status_message, 'Waiting for torrent details from the network...'); @@ -86,13 +85,11 @@ test('createOrUpdateTransfer persists put.io status details across updates', () putio_status_message: '', putio_peers: 2, putio_availability: 11, - downloaded_ever: 108_480_000, }); assert.equal(updated.id, created.id); assert.equal(updated.putio_status_message, ''); assert.equal(updated.putio_peers, 2); assert.equal(updated.putio_availability, 11); - assert.equal(updated.putio_downloaded, 108_480_000); } finally { store.close(); } diff --git a/test/web-download-testids.test.js b/test/web-download-testids.test.js index 31e79e4..04b22e4 100644 --- a/test/web-download-testids.test.js +++ b/test/web-download-testids.test.js @@ -1,7 +1,6 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import { readFileSync } from 'node:fs'; -import { formatPutioStatusDetails } from '../src/web/util.js'; test('downloads UI exposes stable data-testid hooks for frontend tests', () => { const html = readFileSync(new URL('../src/web/index.html', import.meta.url), 'utf8'); @@ -27,22 +26,10 @@ test('downloads UI exposes stable data-testid hooks for frontend tests', () => { } }); -test('downloads UI renders Put.io status messages or transfer metrics below the progress bars', () => { +test('downloads UI renders Put.io status messages or peer availability below the progress bars', () => { const downloadsJs = readFileSync(new URL('../src/web/downloads.js', import.meta.url), 'utf8'); assert.match(downloadsJs, /data-role="putio-status-message"/); - assert.equal(formatPutioStatusDetails({ - lifecycle: 'remote', - putioStatus: 'DOWNLOADING', - putioPeers: 2, - putioAvailability: 11, - putioDownloaded: 108_480_000, - putioUploaded: 0, - putioTotalSize: 931_980_000, - }), '2 peers | downloaded: 103.5 MB of 888.8 MB | uploaded: 0 B of 888.8 MB | availability: 11%'); - assert.equal(formatPutioStatusDetails({ - lifecycle: 'remote', - putioStatus: 'DOWNLOADING', - putioStatusMessage: 'Waiting for torrent details from the network...', - }), 'Waiting for torrent details from the network...'); + assert.match(downloadsJs, /\$\{peerText\} \| availability: \$\{clampPercent\(download\.putioAvailability\)\}%/); + assert.doesNotMatch(downloadsJs, /putioDownloaded|putioUploaded/); });