fix(torrent): use live peer telemetry in properties

- source the Properties peer card from live Aria2 status counts
- distinguish connected peers from unavailable peer details
- remove redundant peer-summary IPC and harden count parsing
- add responsive, accessible peer/seeder presentation and regressions
This commit is contained in:
NimBold
2026-08-15 06:26:25 +03:30
parent de41dd55d6
commit ca32b772a2
23 changed files with 204 additions and 252 deletions
+8
View File
@@ -4,6 +4,7 @@ import {
formatPropertiesDiagnosticCount,
getPropertiesAvailabilityDiagnosticState,
getPropertiesPeerDiagnosticState,
hasLiveTorrentPeerWithoutDetails,
} from './propertiesDiagnostics';
const emptyPeerDiagnostics = {
@@ -49,4 +50,11 @@ describe('Properties peer diagnostics presentation state', () => {
expect(getPropertiesAvailabilityDiagnosticState(null, false, 'idle')).toBe('unavailable');
expect(getPropertiesAvailabilityDiagnosticState(null, false, 'error')).toBe('error');
});
it('distinguishes a live connection from an empty peer-detail snapshot', () => {
expect(hasLiveTorrentPeerWithoutDetails(1, 0)).toBe(true);
expect(hasLiveTorrentPeerWithoutDetails(0, 0)).toBe(false);
expect(hasLiveTorrentPeerWithoutDetails(undefined, 0)).toBe(false);
expect(hasLiveTorrentPeerWithoutDetails(2, 1)).toBe(false);
});
});
+8
View File
@@ -10,6 +10,14 @@ export const formatPropertiesDiagnosticCount = (value: number, locale: string):
return new Intl.NumberFormat(resolveAppLocale(locale)).format(value);
};
export const hasLiveTorrentPeerWithoutDetails = (
connectedPeers: number | undefined,
detailedPeers: number,
): boolean => Number.isSafeInteger(connectedPeers)
&& (connectedPeers ?? 0) > 0
&& Number.isSafeInteger(detailedPeers)
&& detailedPeers === 0;
export const formatPropertiesAvailability = (availability: number, locale: string): string => {
if (!Number.isFinite(availability) || availability < 0) return '—';
return new Intl.NumberFormat(resolveAppLocale(locale), { maximumFractionDigits: 2 }).format(availability);
-23
View File
@@ -1,23 +0,0 @@
import { describe, expect, it } from 'vitest';
import { isCurrentTorrentPeerSummary, isTorrentPeerSummaryStatus } from './propertiesPeerSummary';
describe('Torrent peer summary lifecycle', () => {
it('accepts only the current active Torrent lifecycle', () => {
const request = {
currentDownloadId: 'torrent-1',
requestDownloadId: 'torrent-1',
currentLifecycleEpoch: 8,
requestLifecycleEpoch: 8,
currentStatus: 'downloading',
};
expect(isCurrentTorrentPeerSummary(request)).toBe(true);
expect(isCurrentTorrentPeerSummary({ ...request, currentLifecycleEpoch: 9 })).toBe(false);
expect(isCurrentTorrentPeerSummary({ ...request, requestDownloadId: 'torrent-2' })).toBe(false);
});
it('stops live summary polling for paused and completed lifecycles', () => {
expect(isTorrentPeerSummaryStatus('paused')).toBe(false);
expect(isTorrentPeerSummaryStatus('completed')).toBe(false);
expect(isTorrentPeerSummaryStatus('seeding')).toBe(true);
});
});
-26
View File
@@ -1,26 +0,0 @@
const TORRENT_PEER_SUMMARY_ACTIVE_STATUSES = [
'downloading',
'verifying',
'seeding',
'waitingToSeed',
'retrying',
] as const;
export const isTorrentPeerSummaryStatus = (status: string): boolean =>
(TORRENT_PEER_SUMMARY_ACTIVE_STATUSES as readonly string[]).includes(status);
export const isCurrentTorrentPeerSummary = ({
currentDownloadId,
requestDownloadId,
currentLifecycleEpoch,
requestLifecycleEpoch,
currentStatus,
}: {
currentDownloadId: string | null;
requestDownloadId: string;
currentLifecycleEpoch: number;
requestLifecycleEpoch: number;
currentStatus: string;
}): boolean => currentDownloadId === requestDownloadId
&& currentLifecycleEpoch === requestLifecycleEpoch
&& isTorrentPeerSummaryStatus(currentStatus);
+12 -9
View File
@@ -65,26 +65,29 @@ describe('Properties connection presentation', () => {
})).toEqual({
kind: 'torrent',
showHeaderMetric: true,
labelKey: 'torrentConnectedPeers',
labelKey: 'torrentPeersSeeders',
value: '—',
torrentPeerCounts: {
connectedPeers: undefined,
connectedSeeders: undefined,
},
});
});
it('exposes the explicit live peer and seeder summary values', () => {
it('uses live connected peer and seeder counts from the Properties snapshot', () => {
expect(getPropertiesConnectionPresentation({
isMedia: false,
isTorrent: true,
}, {
totalPeers: 41,
totalSeeders: 2,
torrentConnectedPeers: 10,
torrentConnectedSeeders: 2,
})).toEqual({
kind: 'torrent',
showHeaderMetric: true,
labelKey: 'torrentConnectedPeers',
labelKey: 'torrentPeersSeeders',
value: '—',
torrentPeerSummary: {
totalPeers: 41,
totalSeeders: 2,
torrentPeerCounts: {
connectedPeers: 10,
connectedSeeders: 2,
},
});
});
+11 -9
View File
@@ -2,10 +2,10 @@ import type { PropertiesSnapshot } from '../propertiesBridge';
import { resolveDownloadFraction } from './downloadProgress';
export type PropertiesConnectionKind = 'media' | 'torrent' | 'aria2';
export type PropertiesConnectionLabelKey = 'fragmentConcurrency' | 'torrentConnectedPeers' | 'connections';
export type PropertiesTorrentPeerSummary = {
totalPeers: number;
totalSeeders: number;
export type PropertiesConnectionLabelKey = 'fragmentConcurrency' | 'torrentPeersSeeders' | 'connections';
export type PropertiesTorrentPeerCounts = {
connectedPeers?: number;
connectedSeeders?: number;
};
export type PropertiesConnectionPresentation = {
@@ -13,7 +13,7 @@ export type PropertiesConnectionPresentation = {
showHeaderMetric: boolean;
labelKey: PropertiesConnectionLabelKey;
value: string;
torrentPeerSummary?: PropertiesTorrentPeerSummary;
torrentPeerCounts?: PropertiesTorrentPeerCounts;
};
const displayCount = (value: number | undefined): string => value == null ? '—' : String(value);
@@ -25,8 +25,7 @@ export const getPropertiesProgress = (
: resolveDownloadFraction(snapshot);
export const getPropertiesConnectionPresentation = (
snapshot: Pick<PropertiesSnapshot, 'isMedia' | 'isTorrent' | 'connections' | 'activeConnections' | 'requestedConnections'>,
torrentPeerSummary?: PropertiesTorrentPeerSummary | null,
snapshot: Pick<PropertiesSnapshot, 'isMedia' | 'isTorrent' | 'connections' | 'activeConnections' | 'requestedConnections' | 'torrentConnectedPeers' | 'torrentConnectedSeeders'>,
): PropertiesConnectionPresentation => {
if (snapshot.isMedia === true) {
return {
@@ -41,9 +40,12 @@ export const getPropertiesConnectionPresentation = (
return {
kind: 'torrent',
showHeaderMetric: true,
labelKey: 'torrentConnectedPeers',
labelKey: 'torrentPeersSeeders',
value: '—',
...(torrentPeerSummary ? { torrentPeerSummary } : {}),
torrentPeerCounts: {
connectedPeers: snapshot.torrentConnectedPeers,
connectedSeeders: snapshot.torrentConnectedSeeders,
},
};
}
@@ -0,0 +1,12 @@
import { describe, expect, it } from 'vitest';
import { isTorrentLiveStatus } from './propertiesTorrentLifecycle';
describe('Torrent live lifecycle', () => {
it('identifies statuses with live Aria2 telemetry', () => {
expect(isTorrentLiveStatus('downloading')).toBe(true);
expect(isTorrentLiveStatus('retrying')).toBe(true);
expect(isTorrentLiveStatus('paused')).toBe(false);
expect(isTorrentLiveStatus('completed')).toBe(false);
expect(isTorrentLiveStatus('seeding')).toBe(true);
});
});
+10
View File
@@ -0,0 +1,10 @@
const TORRENT_LIVE_STATUSES = [
'downloading',
'verifying',
'seeding',
'waitingToSeed',
'retrying',
] as const;
export const isTorrentLiveStatus = (status: string): boolean =>
(TORRENT_LIVE_STATUSES as readonly string[]).includes(status);