From 7d1b86810779fce6ed7c6733e08ca2d57f57c47b Mon Sep 17 00:00:00 2001 From: Jose Henrique Date: Wed, 8 Jul 2026 20:01:02 -0300 Subject: [PATCH] adding availability --- main.go | 2 +- project-context.md | 6 +++-- web/app.js | 59 +++++++++++++++++++++++++++++++++++++++++----- web/index.html | 2 +- 4 files changed, 59 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index 6f0e999..6f7dc0e 100644 --- a/main.go +++ b/main.go @@ -162,7 +162,7 @@ var torrentFields = []string{ "id", "name", "totalSize", "downloadedEver", "uploadedEver", "uploadRatio", "percentDone", "status", "eta", "error", "errorString", "doneDate", "isFinished", "rateDownload", "rateUpload", "peersConnected", "peersGettingFromUs", "peersSendingToUs", - "peers", + "availability", "peers", } type rpcRequest struct { diff --git a/project-context.md b/project-context.md index 480ee6b..3aa37f1 100644 --- a/project-context.md +++ b/project-context.md @@ -56,13 +56,15 @@ Torrent list requests should include: id, name, totalSize, downloadedEver, uploadedEver, uploadRatio, percentDone, status, eta, error, errorString, doneDate, isFinished, rateDownload, rateUpload, peersConnected, peersGettingFromUs, peersSendingToUs, -peers +availability, peers ``` Peer objects are expected to include `address`, `clientName`, `progress`, `isDownloadingFrom`, `isUploadingTo`, `rateToClient`, and `rateToPeer` when available. The UI definition of "peers at 100%" is an absolute count of `peers[]` entries whose `progress === 1.0`. Do not reinterpret it as a percentage of connected peers. +The UI availability value comes from Transmission's `availability` per-piece array. Render and sort it as a complete-copy estimate, treating `-1` entries as pieces available locally. + ## Frontend Architecture The frontend is a single page with module-level state in `web/app.js`. It polls on a user-selectable interval, defaults to 3 seconds, and fetches torrents and stats in parallel. @@ -84,7 +86,7 @@ Current sort keys: - `ratio` - `size` - `speed`: orders by combined download and upload speed descending, then download speed, upload speed, and name. -- `peer100`: orders by peers at 100% descending, then connected peers descending, progress descending, and name ascending. +- `availability`: orders by computed availability descending, then progress, then name. ## UI And Design Rules diff --git a/web/app.js b/web/app.js index 1aebaff..5a2872d 100644 --- a/web/app.js +++ b/web/app.js @@ -250,9 +250,50 @@ function activitySpeed(t) { return (t.rateDownload || 0) + (t.rateUpload || 0); } +function availabilityValue(t) { + if (typeof t.availability === "number" && Number.isFinite(t.availability)) { + return t.availability; + } + + const pieces = Array.isArray(t.availability) ? t.availability : []; + if (!pieces.length) return null; + + const counts = []; + let min = Infinity; + for (const piece of pieces) { + const raw = Number(piece); + if (!Number.isFinite(raw)) continue; + const count = raw < 0 ? 1 : raw; + counts.push(count); + if (count < min) min = count; + } + + if (!counts.length) return null; + + let aboveMin = 0; + for (const count of counts) { + if (count > min) aboveMin += 1; + } + + return min + (aboveMin / counts.length); +} + +function availabilitySortValue(t) { + const value = availabilityValue(t); + return value == null ? -1 : value; +} + +function humanAvailability(value) { + if (value == null || !Number.isFinite(value) || value < 0) return "—"; + return `${value.toFixed(value < 10 ? 2 : 1)}x`; +} + function sortTorrents(list) { const key = state.sortKey; const sorted = list.slice(); + const availabilityValues = key === "availability" + ? new Map(sorted.map((t) => [t, availabilitySortValue(t)])) + : null; sorted.sort((a, b) => { switch (key) { case "name": @@ -268,14 +309,10 @@ function sortTorrents(list) { || ((b.rateDownload || 0) - (a.rateDownload || 0)) || ((b.rateUpload || 0) - (a.rateUpload || 0)) || compareName(a, b); - case "peer100": { - const pa = peerStatus(a); - const pb = peerStatus(b); - return (pb.at100 - pa.at100) - || (pb.connected - pa.connected) + case "availability": + return (availabilityValues.get(b) - availabilityValues.get(a)) || ((b.percentDone || 0) - (a.percentDone || 0)) || compareName(a, b); - } case "status": { const ra = STATUS_RANK[classify(a).key] ?? 99; const rb = STATUS_RANK[classify(b).key] ?? 99; @@ -359,6 +396,10 @@ function createCard() { ETA +
+ Availability + +
Peers @ 100% @@ -397,6 +438,7 @@ function updateCard(card, t) { const pctText = (pct * 100).toFixed(pct === 1 ? 0 : 1) + "%"; const isDone = pct >= 1.0; const ps = peerStatus(t); + const availability = availabilityValue(t); const dl = t.rateDownload || 0; const ul = t.rateUpload || 0; const eta = (dl > 0 && !isDone) ? formatETA(t.eta) : (isDone ? "—" : "∞"); @@ -444,6 +486,11 @@ function updateCard(card, t) { setValueClass(ulEl, ul > 0 ? "success" : "dim"); setText(field(card, "eta"), eta); + + const availabilityEl = field(card, "availability"); + setText(availabilityEl, humanAvailability(availability)); + setValueClass(availabilityEl, availability == null ? "dim" : availability >= 1 ? "success" : "warn"); + setText(field(card, "peers-100"), `${ps.at100}/${ps.connected}`); setText(field(card, "finished"), formatDate(t.doneDate)); diff --git a/web/index.html b/web/index.html index fef178c..9847fa7 100644 --- a/web/index.html +++ b/web/index.html @@ -54,7 +54,7 @@ - +