diff --git a/src-tauri/src/ipc.rs b/src-tauri/src/ipc.rs index 756f832..4a465a6 100644 --- a/src-tauri/src/ipc.rs +++ b/src-tauri/src/ipc.rs @@ -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, pub truncated: bool, } diff --git a/src-tauri/src/queue.rs b/src-tauri/src/queue.rs index 82616ac..f407348 100644 --- a/src-tauri/src/queue.rs +++ b/src-tauri/src/queue.rs @@ -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")); diff --git a/src/bindings/TorrentPeerDiagnostics.ts b/src/bindings/TorrentPeerDiagnostics.ts index df286e6..c701f34 100644 --- a/src/bindings/TorrentPeerDiagnostics.ts +++ b/src/bindings/TorrentPeerDiagnostics.ts @@ -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, truncated: boolean, }; +export type TorrentPeerDiagnostics = { listedPeers: number, listedSeeders: number, peers: Array, truncated: boolean, }; diff --git a/src/components/PropertiesWindowApp.tsx b/src/components/PropertiesWindowApp.tsx index 5cbc801..dbc50d0 100644 --- a/src/components/PropertiesWindowApp.tsx +++ b/src/components/PropertiesWindowApp.tsx @@ -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, })} > - {peersValue} + {peersValue} - {seedersValue} + {seedersValue} ; })() : {connectionPresentation.value}; @@ -1446,15 +1447,15 @@ export const PropertiesWindowApp = () => {
{t($ => $.properties.torrentPeerDiagnostics)} -

+

{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 = () => {

{t($ => $.properties.torrentPeerDiagnosticsHint)}

+ {peers && hasTorrentPeerCountDifference( + snapshot.torrentConnectedPeers, + snapshot.torrentConnectedSeeders, + peers.listedPeers, + peers.listedSeeders, + ) &&

+ {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), + })} +

} {peerDiagnosticPhase === 'stale' &&

{t($ => $.properties.torrentPeerDiagnosticsStale)}

} - {peers?.truncated &&

{t($ => $.properties.torrentPeerShowing, { shown: peers.peers.length, total: peers.totalPeers })}

} + {peers?.truncated &&

{t($ => $.properties.torrentPeerShowing, { shown: peers.peers.length, total: peers.listedPeers })}

}
diff --git a/src/i18n/catalogs/en.ts b/src/i18n/catalogs/en.ts index 18a1b45..d426dfb 100644 --- a/src/i18n/catalogs/en.ts +++ b/src/i18n/catalogs/en.ts @@ -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', diff --git a/src/i18n/catalogs/fa.ts b/src/i18n/catalogs/fa.ts index 7836fbf..aab0701 100644 --- a/src/i18n/catalogs/fa.ts +++ b/src/i18n/catalogs/fa.ts @@ -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: 'ثانیه', diff --git a/src/i18n/catalogs/he.ts b/src/i18n/catalogs/he.ts index 206d8e8..2e4c791 100644 --- a/src/i18n/catalogs/he.ts +++ b/src/i18n/catalogs/he.ts @@ -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: 'שניות', diff --git a/src/i18n/catalogs/ru.ts b/src/i18n/catalogs/ru.ts index 3a9f3e1..70a3392 100644 --- a/src/i18n/catalogs/ru.ts +++ b/src/i18n/catalogs/ru.ts @@ -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: 'секунд', diff --git a/src/i18n/catalogs/uk.ts b/src/i18n/catalogs/uk.ts index ce97692..0fcfb9c 100644 --- a/src/i18n/catalogs/uk.ts +++ b/src/i18n/catalogs/uk.ts @@ -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: 'секунд', diff --git a/src/i18n/catalogs/zh-CN.ts b/src/i18n/catalogs/zh-CN.ts index feb9f3b..e6cffeb 100644 --- a/src/i18n/catalogs/zh-CN.ts +++ b/src/i18n/catalogs/zh-CN.ts @@ -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: '秒', diff --git a/src/index.css b/src/index.css index 51ab079..65792d4 100644 --- a/src/index.css +++ b/src/index.css @@ -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; diff --git a/src/utils/propertiesDiagnostics.test.ts b/src/utils/propertiesDiagnostics.test.ts index 89b8b7d..2d42358 100644 --- a/src/utils/propertiesDiagnostics.test.ts +++ b/src/utils/propertiesDiagnostics.test.ts @@ -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'); }); diff --git a/src/utils/propertiesDiagnostics.ts b/src/utils/propertiesDiagnostics.ts index 7266c63..4701e1f 100644 --- a/src/utils/propertiesDiagnostics.ts +++ b/src/utils/propertiesDiagnostics.ts @@ -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,