mirror of
https://github.com/nimbold/Firelink.git
synced 2026-09-01 21:48:00 +00:00
fix(torrent): distinguish connected and listed peers
- label live connected peer telemetry separately from listed peer details - report count mismatches and keep connected values accented - synchronize bindings, locales, accessibility, and regression coverage
This commit is contained in:
@@ -340,9 +340,9 @@ pub struct TorrentPeer {
|
||||
#[ts(export, export_to = "../../src/bindings/")]
|
||||
pub struct TorrentPeerDiagnostics {
|
||||
#[ts(type = "number")]
|
||||
pub total_peers: u32,
|
||||
pub listed_peers: u32,
|
||||
#[ts(type = "number")]
|
||||
pub total_seeders: u32,
|
||||
pub listed_seeders: u32,
|
||||
pub peers: Vec<TorrentPeer>,
|
||||
pub truncated: bool,
|
||||
}
|
||||
|
||||
@@ -6978,8 +6978,8 @@ pub(crate) fn parse_torrent_availability(
|
||||
}
|
||||
|
||||
struct TorrentPeerCounts {
|
||||
total_peers: u32,
|
||||
total_seeders: u32,
|
||||
listed_peers: u32,
|
||||
listed_seeders: u32,
|
||||
}
|
||||
|
||||
fn torrent_peer_counts_from_array(
|
||||
@@ -7001,8 +7001,8 @@ fn torrent_peer_counts_from_array(
|
||||
}
|
||||
}
|
||||
Ok(TorrentPeerCounts {
|
||||
total_peers: u32::try_from(peers.len()).unwrap_or(u32::MAX),
|
||||
total_seeders,
|
||||
listed_peers: u32::try_from(peers.len()).unwrap_or(u32::MAX),
|
||||
listed_seeders: total_seeders,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7032,8 +7032,8 @@ pub(crate) fn parse_torrent_peer_diagnostics(
|
||||
}
|
||||
|
||||
Ok(crate::ipc::TorrentPeerDiagnostics {
|
||||
total_peers: summary.total_peers,
|
||||
total_seeders: summary.total_seeders,
|
||||
listed_peers: summary.listed_peers,
|
||||
listed_seeders: summary.listed_seeders,
|
||||
peers: sanitized,
|
||||
truncated: peers.len() > MAX_TORRENT_PEER_DIAGNOSTICS,
|
||||
})
|
||||
@@ -10035,8 +10035,8 @@ mod tests {
|
||||
}));
|
||||
|
||||
let diagnostics = parse_torrent_peer_diagnostics(serde_json::Value::Array(result)).unwrap();
|
||||
assert_eq!(diagnostics.total_peers, (MAX_TORRENT_PEER_DIAGNOSTICS + 2) as u32);
|
||||
assert_eq!(diagnostics.total_seeders, 2);
|
||||
assert_eq!(diagnostics.listed_peers, (MAX_TORRENT_PEER_DIAGNOSTICS + 2) as u32);
|
||||
assert_eq!(diagnostics.listed_seeders, 2);
|
||||
assert_eq!(diagnostics.peers.len(), MAX_TORRENT_PEER_DIAGNOSTICS);
|
||||
assert!(diagnostics.truncated);
|
||||
assert_eq!(diagnostics.peers[0].ip.as_deref(), Some("192.0.2.10"));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { TorrentPeer } from "./TorrentPeer";
|
||||
|
||||
export type TorrentPeerDiagnostics = { totalPeers: number, totalSeeders: number, peers: Array<TorrentPeer>, truncated: boolean, };
|
||||
export type TorrentPeerDiagnostics = { listedPeers: number, listedSeeders: number, peers: Array<TorrentPeer>, truncated: boolean, };
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
formatPropertiesDiagnosticCount,
|
||||
getPropertiesAvailabilityDiagnosticState,
|
||||
getPropertiesPeerDiagnosticState,
|
||||
hasTorrentPeerCountDifference,
|
||||
hasLiveTorrentPeerWithoutDetails,
|
||||
} from '../utils/propertiesDiagnostics';
|
||||
import { shouldOfferPropertiesUrlExpansion, shouldResetPropertiesUrlExpansion } from '../utils/propertiesUrl';
|
||||
@@ -513,7 +514,7 @@ export const PropertiesWindowApp = () => {
|
||||
setPeers(peerResult.value);
|
||||
setPeerDetailsUnavailable(hasLiveTorrentPeerWithoutDetails(
|
||||
snapshotRef.current?.torrentConnectedPeers,
|
||||
peerResult.value.totalPeers,
|
||||
peerResult.value.listedPeers,
|
||||
));
|
||||
} else if (isExpectedPropertiesDiagnosticUnavailable(peerResult.reason)) {
|
||||
setPeerDetailsUnavailable(hasLiveTorrentPeerWithoutDetails(
|
||||
@@ -1174,9 +1175,9 @@ export const PropertiesWindowApp = () => {
|
||||
seeders: seedersValue,
|
||||
})}
|
||||
>
|
||||
<span className="properties-torrent-peer-count-primary">{peersValue}</span>
|
||||
<span className="properties-torrent-peer-count-connected">{peersValue}</span>
|
||||
<span aria-hidden="true"> / </span>
|
||||
<span>{seedersValue}</span>
|
||||
<span className="properties-torrent-peer-count-connected">{seedersValue}</span>
|
||||
</strong>;
|
||||
})()
|
||||
: <strong>{connectionPresentation.value}</strong>;
|
||||
@@ -1446,15 +1447,15 @@ export const PropertiesWindowApp = () => {
|
||||
<div className="properties-diagnostic-heading">
|
||||
<div className="min-w-0">
|
||||
<span className="properties-diagnostic-label">{t($ => $.properties.torrentPeerDiagnostics)}</span>
|
||||
<p className="properties-diagnostic-value" data-value-state={peerDetailsNotice ? 'unavailable' : peerDiagnosticState} role="status">
|
||||
<p className="properties-diagnostic-value properties-diagnostic-value--secondary" data-value-state={peerDetailsNotice ? 'unavailable' : peerDiagnosticState} role="status">
|
||||
{peerDetailsNotice
|
||||
? t($ => $.properties.torrentPeerDetailsUnavailable, {
|
||||
connected: formatPropertiesDiagnosticCount(snapshot.torrentConnectedPeers ?? 0, snapshot.appearance.locale),
|
||||
})
|
||||
: peers
|
||||
? t($ => $.properties.torrentPeerCount, {
|
||||
total: formatPropertiesDiagnosticCount(peers.totalPeers, snapshot.appearance.locale),
|
||||
seeders: formatPropertiesDiagnosticCount(peers.totalSeeders, snapshot.appearance.locale),
|
||||
listed: formatPropertiesDiagnosticCount(peers.listedPeers, snapshot.appearance.locale),
|
||||
seeders: formatPropertiesDiagnosticCount(peers.listedSeeders, snapshot.appearance.locale),
|
||||
})
|
||||
: diagnosticsLoading
|
||||
? t($ => $.properties.torrentPeerDiagnosticsLoading)
|
||||
@@ -1464,8 +1465,21 @@ export const PropertiesWindowApp = () => {
|
||||
<button type="button" className="app-button px-3 text-xs" aria-busy={diagnosticsLoading || diagnosticsRefreshing} onClick={() => downloadId && void refreshDiagnostics('peers', downloadId, true)}><RefreshCw size={14} className={diagnosticsLoading || diagnosticsRefreshing ? 'animate-spin motion-reduce:animate-none' : undefined} />{t($ => $.properties.torrentPeerDiagnosticsRefresh)}</button>
|
||||
</div>
|
||||
<p className="properties-diagnostic-hint">{t($ => $.properties.torrentPeerDiagnosticsHint)}</p>
|
||||
{peers && hasTorrentPeerCountDifference(
|
||||
snapshot.torrentConnectedPeers,
|
||||
snapshot.torrentConnectedSeeders,
|
||||
peers.listedPeers,
|
||||
peers.listedSeeders,
|
||||
) && <p className="properties-diagnostic-detail" aria-live="polite">
|
||||
{t($ => $.properties.torrentPeerCountDifference, {
|
||||
connectedPeers: formatPropertiesDiagnosticCount(snapshot.torrentConnectedPeers ?? Number.NaN, snapshot.appearance.locale),
|
||||
connectedSeeders: formatPropertiesDiagnosticCount(snapshot.torrentConnectedSeeders ?? Number.NaN, snapshot.appearance.locale),
|
||||
listedPeers: formatPropertiesDiagnosticCount(peers.listedPeers, snapshot.appearance.locale),
|
||||
listedSeeders: formatPropertiesDiagnosticCount(peers.listedSeeders, snapshot.appearance.locale),
|
||||
})}
|
||||
</p>}
|
||||
{peerDiagnosticPhase === 'stale' && <p className="properties-diagnostic-detail">{t($ => $.properties.torrentPeerDiagnosticsStale)}</p>}
|
||||
{peers?.truncated && <p className="properties-diagnostic-detail">{t($ => $.properties.torrentPeerShowing, { shown: peers.peers.length, total: peers.totalPeers })}</p>}
|
||||
{peers?.truncated && <p className="properties-diagnostic-detail">{t($ => $.properties.torrentPeerShowing, { shown: peers.peers.length, total: peers.listedPeers })}</p>}
|
||||
</div>
|
||||
<div className="properties-diagnostic-card" data-diagnostic-phase={availabilityDiagnosticPhase}>
|
||||
<div className="min-w-0">
|
||||
|
||||
@@ -303,13 +303,13 @@ const common = {
|
||||
torrentPeerSpeedLimit: 'Peer speed threshold',
|
||||
torrentMaxPeersInvalid: 'Torrent maximum peers must be an integer from 0 to 1000',
|
||||
torrentPeerSpeedLimitInvalid: 'Torrent peer speed threshold must be greater than zero',
|
||||
torrentPeerDiagnostics: 'Torrent peer diagnostics',
|
||||
torrentPeerDiagnostics: 'Torrent peer details',
|
||||
torrentPeerDiagnosticsRefresh: 'Refresh',
|
||||
torrentPeerDiagnosticsLoading: 'Loading peer diagnostics…',
|
||||
torrentPeerDiagnosticsStale: 'Showing the last validated result; refresh to check again.',
|
||||
torrentPeerDiagnosticsUnavailable: 'Peer diagnostics are available while this Torrent is active or paused.',
|
||||
torrentPeerDiagnosticsFailed: 'Could not read Torrent peer diagnostics.',
|
||||
torrentPeerDiagnosticsHint: 'Validated peer addresses and ports are shown ephemerally; peer IDs and raw bitfields are never retained.',
|
||||
torrentPeerDiagnosticsHint: 'Validated peer addresses and ports are shown ephemerally; peer IDs and raw bitfields are never retained. The connected count above is live Torrent status; this table is a separate peer-list response and may contain a different number.',
|
||||
torrentPeerAddress: 'Peer address',
|
||||
torrentPeerId: 'Peer ID',
|
||||
torrentFileProgress: 'Torrent file progress',
|
||||
@@ -346,22 +346,23 @@ const common = {
|
||||
torrentWebSeedsAdd: 'Add web seed',
|
||||
torrentWebSeedsRemove: 'Remove web seed',
|
||||
torrentWebSeedsInvalid: 'Each web-seed row needs a valid Torrent file and an HTTP(S) base URI without credentials or fragments.',
|
||||
torrentPeerCount: '{{total}} peers — {{seeders}} seeders',
|
||||
torrentPeerCount: '{{listed}} listed peers — {{seeders}} listed seeders',
|
||||
torrentPeerDownload: 'Download',
|
||||
torrentPeerUpload: 'Upload',
|
||||
torrentPeerSeeder: 'Seeder',
|
||||
torrentPeerAmChoking: 'Firelink choking',
|
||||
torrentPeerChoking: 'Peer choking',
|
||||
torrentPeerShowing: 'Showing {{shown}} of {{total}} peers.',
|
||||
torrentPeerShowing: 'Showing {{shown}} of {{total}} listed peers.',
|
||||
torrentStatistics: 'Torrent statistics',
|
||||
torrentUploaded: 'Uploaded',
|
||||
torrentRatio: 'Ratio',
|
||||
torrentSeededDuration: 'Seeded',
|
||||
torrentSeedTimeHint: 'How long this Torrent may continue seeding after its files finish downloading. Leave blank to use the default.',
|
||||
torrentConnectedPeers: 'Peers',
|
||||
torrentPeersSeeders: 'Peers / Seeders',
|
||||
torrentPeersSeeders: 'Connected peers / connected seeders',
|
||||
torrentConnectedPeerMetric: '{{peers}} connected peers / {{seeders}} connected seeders',
|
||||
torrentPeerDetailsUnavailable: '{{connected}} connected peers reported, but peer details are not available yet.',
|
||||
torrentPeerCountDifference: 'Connected status: {{connectedPeers}} peers / {{connectedSeeders}} seeders. The peer-details response lists {{listedPeers}} peers / {{listedSeeders}} seeders.',
|
||||
torrentSeeders: 'Seeders',
|
||||
torrentUploadSpeed: 'Upload speed',
|
||||
seconds: 'seconds',
|
||||
|
||||
@@ -303,13 +303,13 @@ const fa = {
|
||||
torrentPeerSpeedLimit: 'آستانه سرعت همتا',
|
||||
torrentMaxPeersInvalid: 'حداکثر همتاهای تورنت باید عددی صحیح بین ۰ و ۱۰۰۰ باشد',
|
||||
torrentPeerSpeedLimitInvalid: 'آستانه سرعت همتای تورنت باید بیشتر از صفر باشد',
|
||||
torrentPeerDiagnostics: 'اطلاعات همتاهای تورنت',
|
||||
torrentPeerDiagnostics: 'جزئیات همتاهای تورنت',
|
||||
torrentPeerDiagnosticsRefresh: 'تازهسازی',
|
||||
torrentPeerDiagnosticsLoading: 'در حال دریافت اطلاعات همتاها…',
|
||||
torrentPeerDiagnosticsStale: 'آخرین نتیجهٔ معتبر نمایش داده میشود؛ برای بررسی دوباره تازهسازی کنید.',
|
||||
torrentPeerDiagnosticsUnavailable: 'اطلاعات همتاها هنگام فعال یا متوقف بودن تورنت در دسترس است.',
|
||||
torrentPeerDiagnosticsFailed: 'خواندن اطلاعات همتاهای تورنت ممکن نیست.',
|
||||
torrentPeerDiagnosticsHint: 'آدرس و پورت معتبر همتاها فقط بهصورت موقت نمایش داده میشوند؛ شناسه همتا و بیتفیلد خام هرگز نگهداری نمیشود.',
|
||||
torrentPeerDiagnosticsHint: 'آدرس و پورت معتبر همتاها فقط بهصورت موقت نمایش داده میشوند؛ شناسه همتا و بیتفیلد خام هرگز نگهداری نمیشود. تعداد متصلِ بالا وضعیت زندهٔ تورنت است؛ این جدول پاسخ جداگانهای از فهرست همتاهاست و ممکن است تعداد متفاوتی داشته باشد.',
|
||||
torrentPeerAddress: 'نشانی همتا',
|
||||
torrentPeerId: 'شناسهٔ همتا',
|
||||
torrentFileProgress: 'پیشرفت فایلهای تورنت',
|
||||
@@ -346,13 +346,13 @@ const fa = {
|
||||
torrentWebSeedsAdd: 'افزودن وبسید',
|
||||
torrentWebSeedsRemove: 'حذف وبسید',
|
||||
torrentWebSeedsInvalid: 'هر ردیف وبسید باید فایل معتبر تورنت و نشانی پایهٔ HTTP(S) بدون اطلاعات ورود یا fragment داشته باشد.',
|
||||
torrentPeerCount: '{{total}} همتا — {{seeders}} سید',
|
||||
torrentPeerCount: '{{listed}} همتای فهرستشده — {{seeders}} سید فهرستشده',
|
||||
torrentPeerDownload: 'دریافت',
|
||||
torrentPeerUpload: 'آپلود',
|
||||
torrentPeerSeeder: 'سید',
|
||||
torrentPeerAmChoking: 'محدودسازی از طرف Firelink',
|
||||
torrentPeerChoking: 'محدودسازی از طرف همتا',
|
||||
torrentPeerShowing: 'نمایش {{shown}} همتا از {{total}} همتا.',
|
||||
torrentPeerShowing: 'نمایش {{shown}} همتا از {{total}} همتای فهرستشده.',
|
||||
torrentStatistics: 'آمار تورنت',
|
||||
torrentUploaded: 'آپلودشده',
|
||||
torrentRatio: 'نسبت',
|
||||
@@ -362,6 +362,7 @@ const fa = {
|
||||
torrentPeersSeeders: 'همتاهای متصل / سیدهای متصل',
|
||||
torrentConnectedPeerMetric: '{{peers}} همتای متصل / {{seeders}} سید متصل',
|
||||
torrentPeerDetailsUnavailable: '{{connected}} همتای متصل گزارش شده، اما جزئیات همتاها هنوز در دسترس نیست.',
|
||||
torrentPeerCountDifference: 'وضعیت اتصال: {{connectedPeers}} همتا / {{connectedSeeders}} سید. پاسخ جزئیات همتاها {{listedPeers}} همتا / {{listedSeeders}} سید را فهرست کرده است.',
|
||||
torrentSeeders: 'سیدها',
|
||||
torrentUploadSpeed: 'سرعت آپلود',
|
||||
seconds: 'ثانیه',
|
||||
|
||||
@@ -303,13 +303,13 @@ const he = {
|
||||
torrentPeerSpeedLimit: 'סף מהירות עמיתים',
|
||||
torrentMaxPeersInvalid: 'מספר העמיתים המרבי חייב להיות מספר שלם בין 0 ל-1000',
|
||||
torrentPeerSpeedLimitInvalid: 'סף מהירות העמיתים חייב להיות גדול מאפס',
|
||||
torrentPeerDiagnostics: 'אבחון עמיתי טורנט',
|
||||
torrentPeerDiagnostics: 'פרטי עמיתי טורנט',
|
||||
torrentPeerDiagnosticsRefresh: 'רענון',
|
||||
torrentPeerDiagnosticsLoading: 'טוען אבחון עמיתים…',
|
||||
torrentPeerDiagnosticsStale: 'מוצגת התוצאה המאומתת האחרונה; רענן כדי לבדוק שוב.',
|
||||
torrentPeerDiagnosticsUnavailable: 'אבחון עמיתים זמין כשהטורנט פעיל או מושהה.',
|
||||
torrentPeerDiagnosticsFailed: 'לא ניתן לקרוא את אבחון עמיתי הטורנט.',
|
||||
torrentPeerDiagnosticsHint: 'כתובות ויציאות מאומתות של עמיתים מוצגות באופן זמני בלבד; מזהי עמיתים ושדות סיביות גולמיים לעולם אינם נשמרים.',
|
||||
torrentPeerDiagnosticsHint: 'כתובות ויציאות מאומתות של עמיתים מוצגות באופן זמני בלבד; מזהי עמיתים ושדות סיביות גולמיים לעולם אינם נשמרים. המספר המחובר למעלה הוא מצב הטורנט בזמן אמת; הטבלה הזו היא תגובה נפרדת של רשימת העמיתים וייתכן שתציג מספר אחר.',
|
||||
torrentPeerAddress: 'כתובת עמית',
|
||||
torrentPeerId: 'מזהה עמית',
|
||||
torrentFileProgress: 'התקדמות קובצי הטורנט',
|
||||
@@ -346,22 +346,23 @@ const he = {
|
||||
torrentWebSeedsAdd: 'הוסף זריעת Web',
|
||||
torrentWebSeedsRemove: 'הסר זריעת Web',
|
||||
torrentWebSeedsInvalid: 'כל שורת זריעת Web צריכה קובץ טורנט תקין וכתובת בסיס HTTP(S) ללא פרטי התחברות או fragment.',
|
||||
torrentPeerCount: '{{total}} עמיתים — {{seeders}} משתפים',
|
||||
torrentPeerCount: '{{listed}} עמיתים ברשימה — {{seeders}} משתפים ברשימה',
|
||||
torrentPeerDownload: 'הורדה',
|
||||
torrentPeerUpload: 'העלאה',
|
||||
torrentPeerSeeder: 'משתף',
|
||||
torrentPeerAmChoking: 'Firelink מגביל',
|
||||
torrentPeerChoking: 'העמית מגביל',
|
||||
torrentPeerShowing: 'מוצגים {{shown}} מתוך {{total}} עמיתים.',
|
||||
torrentPeerShowing: 'מוצגים {{shown}} מתוך {{total}} עמיתים ברשימה.',
|
||||
torrentStatistics: 'סטטיסטיקות טורנט',
|
||||
torrentUploaded: 'הועלה',
|
||||
torrentRatio: 'יחס',
|
||||
torrentSeededDuration: 'משך שיתוף',
|
||||
torrentSeedTimeHint: 'משך הזמן שבו הטורנט ימשיך לשתף לאחר סיום ההורדה. השאר ריק כדי להשתמש בברירת המחדל.',
|
||||
torrentConnectedPeers: 'עמיתים',
|
||||
torrentPeersSeeders: 'עמיתים / משתפים',
|
||||
torrentPeersSeeders: 'עמיתים מחוברים / משתפים מחוברים',
|
||||
torrentConnectedPeerMetric: '{{peers}} עמיתים מחוברים / {{seeders}} משתפים מחוברים',
|
||||
torrentPeerDetailsUnavailable: 'דווחו {{connected}} עמיתים מחוברים, אך פרטי העמיתים עדיין אינם זמינים.',
|
||||
torrentPeerCountDifference: 'מחוברים: {{connectedPeers}} עמיתים / {{connectedSeeders}} משתפים. תגובת פרטי העמיתים מציגה {{listedPeers}} עמיתים / {{listedSeeders}} משתפים.',
|
||||
torrentSeeders: 'משתפים',
|
||||
torrentUploadSpeed: 'מהירות העלאה',
|
||||
seconds: 'שניות',
|
||||
|
||||
@@ -303,13 +303,13 @@ const ru = {
|
||||
torrentPeerSpeedLimit: 'Порог скорости пиров',
|
||||
torrentMaxPeersInvalid: 'Максимум пиров должен быть целым числом от 0 до 1000',
|
||||
torrentPeerSpeedLimitInvalid: 'Порог скорости пиров должен быть больше нуля',
|
||||
torrentPeerDiagnostics: 'Диагностика пиров торрента',
|
||||
torrentPeerDiagnostics: 'Сведения о пирах торрента',
|
||||
torrentPeerDiagnosticsRefresh: 'Обновить',
|
||||
torrentPeerDiagnosticsLoading: 'Загрузка диагностики пиров…',
|
||||
torrentPeerDiagnosticsStale: 'Показан последний проверенный результат; обновите, чтобы проверить снова.',
|
||||
torrentPeerDiagnosticsUnavailable: 'Диагностика пиров доступна, пока торрент активен или приостановлен.',
|
||||
torrentPeerDiagnosticsFailed: 'Не удалось получить диагностику пиров торрента.',
|
||||
torrentPeerDiagnosticsHint: 'Проверенные адреса и порты пиров показываются только временно; идентификаторы пиров и исходные битовые поля никогда не сохраняются.',
|
||||
torrentPeerDiagnosticsHint: 'Проверенные адреса и порты пиров показываются только временно; идентификаторы пиров и исходные битовые поля никогда не сохраняются. Число подключений выше — текущее состояние Torrent; эта таблица получена отдельным ответом со списком пиров и может содержать другое число.',
|
||||
torrentPeerAddress: 'Адрес пира',
|
||||
torrentPeerId: 'ID пира',
|
||||
torrentFileProgress: 'Прогресс файлов торрента',
|
||||
@@ -346,22 +346,23 @@ const ru = {
|
||||
torrentWebSeedsAdd: 'Добавить веб-сид',
|
||||
torrentWebSeedsRemove: 'Удалить веб-сид',
|
||||
torrentWebSeedsInvalid: 'В каждой строке веб-сида нужны допустимый файл торрента и базовый HTTP(S)-адрес без учётных данных или фрагмента.',
|
||||
torrentPeerCount: '{{total}} пиров — {{seeders}} сидеров',
|
||||
torrentPeerCount: '{{listed}} пиров в списке — {{seeders}} сидов в списке',
|
||||
torrentPeerDownload: 'Загрузка',
|
||||
torrentPeerUpload: 'Отдача',
|
||||
torrentPeerSeeder: 'Сидер',
|
||||
torrentPeerAmChoking: 'Firelink ограничивает',
|
||||
torrentPeerChoking: 'Пир ограничивает',
|
||||
torrentPeerShowing: 'Показано {{shown}} из {{total}} пиров.',
|
||||
torrentPeerShowing: 'Показано {{shown}} из {{total}} пиров в списке.',
|
||||
torrentStatistics: 'Статистика торрента',
|
||||
torrentUploaded: 'Отдано',
|
||||
torrentRatio: 'Коэффициент',
|
||||
torrentSeededDuration: 'Время раздачи',
|
||||
torrentSeedTimeHint: 'Как долго Torrent продолжает раздачу после завершения загрузки. Оставьте пустым для значения по умолчанию.',
|
||||
torrentConnectedPeers: 'Пиры',
|
||||
torrentPeersSeeders: 'Пиры / Сиды',
|
||||
torrentPeersSeeders: 'Подключённые пиры / подключённые сиды',
|
||||
torrentConnectedPeerMetric: '{{peers}} подключённых пиров / {{seeders}} подключённых сидов',
|
||||
torrentPeerDetailsUnavailable: 'Подключённых пиров: {{connected}}, но сведения о них пока недоступны.',
|
||||
torrentPeerCountDifference: 'Подключено: {{connectedPeers}} пиров / {{connectedSeeders}} сидов. В ответе со сведениями о пирах указано: {{listedPeers}} пиров / {{listedSeeders}} сидов.',
|
||||
torrentSeeders: 'Сиды',
|
||||
torrentUploadSpeed: 'Скорость отдачи',
|
||||
seconds: 'секунд',
|
||||
|
||||
@@ -303,13 +303,13 @@ const uk = {
|
||||
torrentPeerSpeedLimit: 'Поріг швидкості пірів',
|
||||
torrentMaxPeersInvalid: 'Максимум пірів має бути цілим числом від 0 до 1000',
|
||||
torrentPeerSpeedLimitInvalid: 'Поріг швидкості пірів має бути більшим за нуль',
|
||||
torrentPeerDiagnostics: 'Діагностика пірів торрента',
|
||||
torrentPeerDiagnostics: 'Відомості про піри торрента',
|
||||
torrentPeerDiagnosticsRefresh: 'Оновити',
|
||||
torrentPeerDiagnosticsLoading: 'Завантаження діагностики пірів…',
|
||||
torrentPeerDiagnosticsStale: 'Показано останній перевірений результат; оновіть, щоб перевірити ще раз.',
|
||||
torrentPeerDiagnosticsUnavailable: 'Діагностика пірів доступна, поки торрент активний або призупинений.',
|
||||
torrentPeerDiagnosticsFailed: 'Не вдалося отримати діагностику пірів торрента.',
|
||||
torrentPeerDiagnosticsHint: 'Перевірені адреси й порти пірів показуються лише тимчасово; ідентифікатори пірів і сирі бітові поля ніколи не зберігаються.',
|
||||
torrentPeerDiagnosticsHint: 'Перевірені адреси й порти пірів показуються лише тимчасово; ідентифікатори пірів і сирі бітові поля ніколи не зберігаються. Кількість підключень вище — це поточний стан торента; ця таблиця отримана окремою відповіддю зі списком пірів і може містити інше число.',
|
||||
torrentPeerAddress: 'Адреса піра',
|
||||
torrentPeerId: 'ID піра',
|
||||
torrentFileProgress: 'Прогрес файлів торрента',
|
||||
@@ -346,22 +346,23 @@ const uk = {
|
||||
torrentWebSeedsAdd: 'Додати вебсід',
|
||||
torrentWebSeedsRemove: 'Видалити вебсід',
|
||||
torrentWebSeedsInvalid: 'Кожен рядок вебсіду має містити дійсний файл торента й базову HTTP(S)-адресу без облікових даних або фрагмента.',
|
||||
torrentPeerCount: '{{total}} пірів — {{seeders}} сідів',
|
||||
torrentPeerCount: '{{listed}} пірів у списку — {{seeders}} сідів у списку',
|
||||
torrentPeerDownload: 'Завантаження',
|
||||
torrentPeerUpload: 'Віддача',
|
||||
torrentPeerSeeder: 'Сідер',
|
||||
torrentPeerAmChoking: 'Firelink обмежує',
|
||||
torrentPeerChoking: 'Пір обмежує',
|
||||
torrentPeerShowing: 'Показано {{shown}} із {{total}} пірів.',
|
||||
torrentPeerShowing: 'Показано {{shown}} із {{total}} пірів у списку.',
|
||||
torrentStatistics: 'Статистика торента',
|
||||
torrentUploaded: 'Віддано',
|
||||
torrentRatio: 'Коефіцієнт',
|
||||
torrentSeededDuration: 'Час роздачі',
|
||||
torrentSeedTimeHint: 'Як довго Torrent продовжує роздачу після завершення завантаження. Залиште порожнім для значення за замовчуванням.',
|
||||
torrentConnectedPeers: 'Піри',
|
||||
torrentPeersSeeders: 'Піри / Сіди',
|
||||
torrentPeersSeeders: 'Підключені піри / підключені сіди',
|
||||
torrentConnectedPeerMetric: '{{peers}} підключених пірів / {{seeders}} підключених сідів',
|
||||
torrentPeerDetailsUnavailable: 'Підключених пірів: {{connected}}, але відомості про них поки недоступні.',
|
||||
torrentPeerCountDifference: 'Підключено: {{connectedPeers}} пірів / {{connectedSeeders}} сідів. У відповіді з відомостями про піри зазначено: {{listedPeers}} пірів / {{listedSeeders}} сідів.',
|
||||
torrentSeeders: 'Сіди',
|
||||
torrentUploadSpeed: 'Швидкість віддачі',
|
||||
seconds: 'секунд',
|
||||
|
||||
@@ -303,13 +303,13 @@ const zhCN = {
|
||||
torrentPeerSpeedLimit: '对等节点速度阈值',
|
||||
torrentMaxPeersInvalid: 'Torrent 最大对等节点数必须是 0 到 1000 之间的整数',
|
||||
torrentPeerSpeedLimitInvalid: '对等节点速度阈值必须大于零',
|
||||
torrentPeerDiagnostics: 'Torrent 对等节点诊断',
|
||||
torrentPeerDiagnostics: 'Torrent 对等节点详情',
|
||||
torrentPeerDiagnosticsRefresh: '刷新',
|
||||
torrentPeerDiagnosticsLoading: '正在加载对等节点诊断…',
|
||||
torrentPeerDiagnosticsStale: '当前显示最近一次验证的结果;刷新以再次检查。',
|
||||
torrentPeerDiagnosticsUnavailable: 'Torrent 活跃或暂停时可查看对等节点诊断。',
|
||||
torrentPeerDiagnosticsFailed: '无法读取 Torrent 对等节点诊断。',
|
||||
torrentPeerDiagnosticsHint: '仅临时显示经过验证的对等端地址和端口;永不保留对等端 ID 或原始位域。',
|
||||
torrentPeerDiagnosticsHint: '仅临时显示经过验证的对等端地址和端口;永不保留对等端 ID 或原始位域。上方的连接数是 Torrent 的实时状态;此表来自单独的对等节点列表响应,数量可能不同。',
|
||||
torrentPeerAddress: '节点地址',
|
||||
torrentPeerId: '节点 ID',
|
||||
torrentFileProgress: 'Torrent 文件进度',
|
||||
@@ -346,22 +346,23 @@ const zhCN = {
|
||||
torrentWebSeedsAdd: '添加 Web 做种',
|
||||
torrentWebSeedsRemove: '移除 Web 做种',
|
||||
torrentWebSeedsInvalid: '每行 Web 做种都需要有效的 Torrent 文件和不含凭据或片段的 HTTP(S) 基础地址。',
|
||||
torrentPeerCount: '{{total}} 个节点 — {{seeders}} 个做种节点',
|
||||
torrentPeerCount: '{{listed}} 个列表节点 — {{seeders}} 个列表做种节点',
|
||||
torrentPeerDownload: '下载',
|
||||
torrentPeerUpload: '上传',
|
||||
torrentPeerSeeder: '做种',
|
||||
torrentPeerAmChoking: 'Firelink 限制中',
|
||||
torrentPeerChoking: '对等节点限制中',
|
||||
torrentPeerShowing: '显示 {{total}} 个节点中的 {{shown}} 个。',
|
||||
torrentPeerShowing: '显示列表中的 {{shown}}/{{total}} 个节点。',
|
||||
torrentStatistics: '种子统计',
|
||||
torrentUploaded: '已上传',
|
||||
torrentRatio: '分享率',
|
||||
torrentSeededDuration: '做种时长',
|
||||
torrentSeedTimeHint: '文件下载完成后继续做种的时长。留空以使用默认值。',
|
||||
torrentConnectedPeers: '连接数',
|
||||
torrentPeersSeeders: '节点 / 做种',
|
||||
torrentPeersSeeders: '已连接节点 / 已连接做种节点',
|
||||
torrentConnectedPeerMetric: '{{peers}} 个已连接节点 / {{seeders}} 个已连接做种节点',
|
||||
torrentPeerDetailsUnavailable: '检测到 {{connected}} 个已连接节点,但其详细信息暂时不可用。',
|
||||
torrentPeerCountDifference: '连接状态:{{connectedPeers}} 个节点 / {{connectedSeeders}} 个做种节点。对等节点详情响应列出 {{listedPeers}} 个节点 / {{listedSeeders}} 个做种节点。',
|
||||
torrentSeeders: '种子数',
|
||||
torrentUploadSpeed: '上传速度',
|
||||
seconds: '秒',
|
||||
|
||||
+5
-1
@@ -868,7 +868,7 @@ html[data-list-density="relaxed"] {
|
||||
text-overflow: clip;
|
||||
}
|
||||
|
||||
.properties-metric-card .properties-torrent-peer-count-primary {
|
||||
.properties-metric-card .properties-torrent-peer-count-connected {
|
||||
color: hsl(var(--accent-color));
|
||||
}
|
||||
|
||||
@@ -1064,6 +1064,10 @@ html[data-list-density="relaxed"] {
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.properties-diagnostic-value--secondary {
|
||||
color: hsl(var(--text-primary));
|
||||
}
|
||||
|
||||
.properties-diagnostic-hint {
|
||||
max-width: 60ch;
|
||||
margin-top: 6px;
|
||||
|
||||
@@ -4,12 +4,13 @@ import {
|
||||
formatPropertiesDiagnosticCount,
|
||||
getPropertiesAvailabilityDiagnosticState,
|
||||
getPropertiesPeerDiagnosticState,
|
||||
hasTorrentPeerCountDifference,
|
||||
hasLiveTorrentPeerWithoutDetails,
|
||||
} from './propertiesDiagnostics';
|
||||
|
||||
const emptyPeerDiagnostics = {
|
||||
totalPeers: 0,
|
||||
totalSeeders: 0,
|
||||
listedPeers: 0,
|
||||
listedSeeders: 0,
|
||||
peers: [],
|
||||
truncated: false,
|
||||
};
|
||||
@@ -29,6 +30,15 @@ describe('Properties peer diagnostics presentation state', () => {
|
||||
expect(formatPropertiesDiagnosticCount(Number.MAX_SAFE_INTEGER + 1, 'en-US')).toBe('—');
|
||||
});
|
||||
|
||||
it('identifies when the connected telemetry differs from the listed peer response', () => {
|
||||
expect(hasTorrentPeerCountDifference(38, 2, 5, 2)).toBe(true);
|
||||
expect(hasTorrentPeerCountDifference(5, 2, 5, 2)).toBe(false);
|
||||
expect(hasTorrentPeerCountDifference(undefined, 2, 5, 2)).toBe(false);
|
||||
expect(hasTorrentPeerCountDifference(38, 2, 5, 1)).toBe(true);
|
||||
expect(hasTorrentPeerCountDifference(Number.NaN, 2, 5, 2)).toBe(false);
|
||||
expect(hasTorrentPeerCountDifference(38, 2, Number.POSITIVE_INFINITY, 2)).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps a genuine empty response live instead of treating it as unavailable', () => {
|
||||
expect(getPropertiesPeerDiagnosticState(emptyPeerDiagnostics, false, 'idle')).toBe('live');
|
||||
});
|
||||
|
||||
@@ -5,11 +5,29 @@ import type { PropertiesDiagnosticPhase } from '../propertiesBridge';
|
||||
|
||||
export type PropertiesDiagnosticValueState = 'live' | 'loading' | 'stale' | 'error' | 'unavailable';
|
||||
|
||||
const isValidDiagnosticCount = (value: number | undefined): value is number =>
|
||||
typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
||||
|
||||
export const formatPropertiesDiagnosticCount = (value: number, locale: string): string => {
|
||||
if (!Number.isSafeInteger(value) || value < 0) return '—';
|
||||
if (!isValidDiagnosticCount(value)) return '—';
|
||||
return new Intl.NumberFormat(resolveAppLocale(locale)).format(value);
|
||||
};
|
||||
|
||||
export const hasTorrentPeerCountDifference = (
|
||||
connectedPeers: number | undefined,
|
||||
connectedSeeders: number | undefined,
|
||||
listedPeers: number,
|
||||
listedSeeders: number,
|
||||
): boolean => (
|
||||
isValidDiagnosticCount(connectedPeers)
|
||||
&& isValidDiagnosticCount(listedPeers)
|
||||
&& connectedPeers !== listedPeers
|
||||
) || (
|
||||
isValidDiagnosticCount(connectedSeeders)
|
||||
&& isValidDiagnosticCount(listedSeeders)
|
||||
&& connectedSeeders !== listedSeeders
|
||||
);
|
||||
|
||||
export const hasLiveTorrentPeerWithoutDetails = (
|
||||
connectedPeers: number | undefined,
|
||||
detailedPeers: number,
|
||||
|
||||
Reference in New Issue
Block a user