adding availability
This commit is contained in:
2
main.go
2
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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
59
web/app.js
59
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() {
|
||||
<span class="k">ETA</span>
|
||||
<span class="v" data-field="eta"></span>
|
||||
</div>
|
||||
<div class="stat-cell">
|
||||
<span class="k">Availability</span>
|
||||
<span class="v mono" data-field="availability"></span>
|
||||
</div>
|
||||
<div class="stat-cell">
|
||||
<span class="k">Peers @ 100%</span>
|
||||
<span class="v mono" data-field="peers-100"></span>
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<button class="sort-btn" data-sort="ratio">Ratio</button>
|
||||
<button class="sort-btn" data-sort="size">Size</button>
|
||||
<button class="sort-btn" data-sort="speed">Speed</button>
|
||||
<button class="sort-btn" data-sort="peer100">100% Peers</button>
|
||||
<button class="sort-btn" data-sort="availability">Availability</button>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<label class="interval-control" for="refresh-interval">
|
||||
|
||||
Reference in New Issue
Block a user