mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-04 00:18:41 +00:00
feat(torrents): add peer diagnostics
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type TorrentPeer = { downloadSpeed: number, uploadSpeed: number, seeder: boolean, amChoking: boolean, peerChoking: boolean, };
|
||||
@@ -0,0 +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, };
|
||||
@@ -3,6 +3,8 @@ import { useDownloadStore, DownloadItem } from '../store/useDownloadStore';
|
||||
import { useDownloadProgressStore } from '../store/downloadProgressStore';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useSettingsStore } from '../store/useSettingsStore';
|
||||
import type { TorrentPeerDiagnostics } from '../bindings/TorrentPeerDiagnostics';
|
||||
import { invokeCommand as invoke } from '../ipc';
|
||||
import { ChevronDown, ChevronRight, FolderPlus, Info, CheckCircle, AlertCircle, Play, Pause } from 'lucide-react';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { resolveCategoryDestination } from '../utils/downloadLocations';
|
||||
@@ -13,6 +15,7 @@ import {
|
||||
} from '../utils/downloadActions';
|
||||
import {
|
||||
downloadProgressColorClass,
|
||||
formatDownloadBytes,
|
||||
formatDownloadTotal,
|
||||
resolveDownloadSizeDisplay
|
||||
} from '../utils/downloadProgress';
|
||||
@@ -36,6 +39,12 @@ const formatLastTry = (
|
||||
});
|
||||
};
|
||||
|
||||
const isPeerDiagnosticsStatus = (status: string): boolean =>
|
||||
['downloading', 'seeding', 'retrying'].includes(status);
|
||||
|
||||
const formatPeerSpeed = (bytesPerSecond: number): string =>
|
||||
`${formatDownloadBytes(bytesPerSecond)}/s`;
|
||||
|
||||
export const PropertiesModal = () => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const categoryLabel = (category: string) => {
|
||||
@@ -80,6 +89,9 @@ export const PropertiesModal = () => {
|
||||
const [torrentCheckIntegrity, setTorrentCheckIntegrity] = useState(false);
|
||||
const [torrentTrackers, setTorrentTrackers] = useState('');
|
||||
const [torrentStopTimeout, setTorrentStopTimeout] = useState('0');
|
||||
const [torrentPeerDiagnostics, setTorrentPeerDiagnostics] = useState<TorrentPeerDiagnostics | null>(null);
|
||||
const [torrentPeerDiagnosticsError, setTorrentPeerDiagnosticsError] = useState(false);
|
||||
const [isTorrentPeerDiagnosticsPending, setIsTorrentPeerDiagnosticsPending] = useState(false);
|
||||
const [isLiveSpeedLimitPending, setIsLiveSpeedLimitPending] = useState(false);
|
||||
const [isLiveTorrentUploadLimitPending, setIsLiveTorrentUploadLimitPending] = useState(false);
|
||||
const [isLiveTorrentPeerOptionsPending, setIsLiveTorrentPeerOptionsPending] = useState(false);
|
||||
@@ -99,6 +111,7 @@ export const PropertiesModal = () => {
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [isPauseResumePending, setIsPauseResumePending] = useState(false);
|
||||
const actionRequestRef = useRef(0);
|
||||
const peerDiagnosticsRequestRef = useRef(0);
|
||||
const modalRef = useModalFocus(Boolean(selectedPropertiesDownloadId && item));
|
||||
|
||||
useEffect(() => {
|
||||
@@ -108,6 +121,10 @@ export const PropertiesModal = () => {
|
||||
setIsLiveSpeedLimitPending(false);
|
||||
setIsLiveTorrentUploadLimitPending(false);
|
||||
setIsLiveTorrentPeerOptionsPending(false);
|
||||
peerDiagnosticsRequestRef.current += 1;
|
||||
setTorrentPeerDiagnostics(null);
|
||||
setTorrentPeerDiagnosticsError(false);
|
||||
setIsTorrentPeerDiagnosticsPending(false);
|
||||
}, [selectedPropertiesDownloadId]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -191,6 +208,13 @@ export const PropertiesModal = () => {
|
||||
setLiveTorrentUploadLimitValue(activeLimit && activeLimit !== '0' ? activeLimit : '');
|
||||
}, [item?.torrentUploadLimit, selectedPropertiesDownloadId]);
|
||||
|
||||
useEffect(() => {
|
||||
peerDiagnosticsRequestRef.current += 1;
|
||||
setTorrentPeerDiagnostics(null);
|
||||
setTorrentPeerDiagnosticsError(false);
|
||||
setIsTorrentPeerDiagnosticsPending(false);
|
||||
}, [item?.id, item?.isTorrent, item?.lastTry, item?.status]);
|
||||
|
||||
useEffect(() => {
|
||||
setLiveTorrentMaxPeersValue(
|
||||
item?.torrentMaxPeers === undefined ? '' : String(item.torrentMaxPeers)
|
||||
@@ -243,6 +267,46 @@ export const PropertiesModal = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleRefreshTorrentPeers = async () => {
|
||||
if (
|
||||
isTorrentPeerDiagnosticsPending
|
||||
|| !item.isTorrent
|
||||
|| !isPeerDiagnosticsStatus(item.status)
|
||||
) return;
|
||||
|
||||
const requestId = ++peerDiagnosticsRequestRef.current;
|
||||
const propertiesDownloadId = item.id;
|
||||
setIsTorrentPeerDiagnosticsPending(true);
|
||||
setTorrentPeerDiagnosticsError(false);
|
||||
try {
|
||||
const diagnostics = await invoke('get_torrent_peers', { id: propertiesDownloadId });
|
||||
const currentItem = useDownloadStore.getState().downloads.find(download => download.id === propertiesDownloadId);
|
||||
if (
|
||||
requestId === peerDiagnosticsRequestRef.current
|
||||
&& useDownloadStore.getState().selectedPropertiesDownloadId === propertiesDownloadId
|
||||
&& currentItem?.isTorrent
|
||||
&& isPeerDiagnosticsStatus(currentItem.status)
|
||||
) {
|
||||
setTorrentPeerDiagnostics(diagnostics);
|
||||
}
|
||||
} catch {
|
||||
const currentItem = useDownloadStore.getState().downloads.find(download => download.id === propertiesDownloadId);
|
||||
if (
|
||||
requestId === peerDiagnosticsRequestRef.current
|
||||
&& useDownloadStore.getState().selectedPropertiesDownloadId === propertiesDownloadId
|
||||
&& currentItem?.isTorrent
|
||||
&& isPeerDiagnosticsStatus(currentItem.status)
|
||||
) {
|
||||
setTorrentPeerDiagnosticsError(true);
|
||||
setTorrentPeerDiagnostics(null);
|
||||
}
|
||||
} finally {
|
||||
if (requestId === peerDiagnosticsRequestRef.current) {
|
||||
setIsTorrentPeerDiagnosticsPending(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!url.trim()) {
|
||||
setErrorMessage(t($ => $.properties.enterValidUrl));
|
||||
@@ -459,6 +523,7 @@ export const PropertiesModal = () => {
|
||||
const liveSpeedLimitUnavailable = item.isMedia && ['downloading', 'processing', 'retrying'].includes(item.status);
|
||||
const liveTorrentUploadLimitAvailable = item.isTorrent && ['downloading', 'seeding', 'retrying'].includes(item.status);
|
||||
const liveTorrentPeerOptionsAvailable = item.isTorrent && ['downloading', 'seeding', 'retrying'].includes(item.status);
|
||||
const torrentPeerDiagnosticsAvailable = item.isTorrent && isPeerDiagnosticsStatus(item.status);
|
||||
const configuredConnections = resolveDownloadConnections(item.connections, perServerConnections);
|
||||
const observedConnectionTotal = Math.max(
|
||||
1,
|
||||
@@ -725,6 +790,80 @@ export const PropertiesModal = () => {
|
||||
<div className="col-start-2 text-[11px] text-text-muted">
|
||||
{t($ => $.properties.torrentPeerOptionsSavedHint)}
|
||||
</div>
|
||||
<div className="col-start-2 rounded-lg border border-border-modal bg-bg-input/30 p-3 space-y-2">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<div className="text-xs font-semibold text-text-primary">
|
||||
{t($ => $.properties.torrentPeerDiagnostics)}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void handleRefreshTorrentPeers()}
|
||||
disabled={!torrentPeerDiagnosticsAvailable || isTorrentPeerDiagnosticsPending}
|
||||
className="app-button px-3 text-xs disabled:opacity-50"
|
||||
>
|
||||
{isTorrentPeerDiagnosticsPending
|
||||
? t($ => $.properties.torrentPeerDiagnosticsLoading)
|
||||
: t($ => $.properties.torrentPeerDiagnosticsRefresh)}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-[11px] text-text-muted">
|
||||
{t($ => $.properties.torrentPeerDiagnosticsHint)}
|
||||
</p>
|
||||
{!torrentPeerDiagnosticsAvailable && (
|
||||
<p className="text-[11px] text-text-muted">
|
||||
{t($ => $.properties.torrentPeerDiagnosticsUnavailable)}
|
||||
</p>
|
||||
)}
|
||||
{torrentPeerDiagnosticsError && (
|
||||
<p className="text-[11px] text-red-400">
|
||||
{t($ => $.properties.torrentPeerDiagnosticsFailed)}
|
||||
</p>
|
||||
)}
|
||||
{torrentPeerDiagnostics && (
|
||||
<>
|
||||
<div className="text-[11px] font-medium text-text-primary">
|
||||
{t($ => $.properties.torrentPeerCount, {
|
||||
total: torrentPeerDiagnostics.totalPeers,
|
||||
seeders: torrentPeerDiagnostics.totalSeeders
|
||||
})}
|
||||
</div>
|
||||
<div className="max-h-48 overflow-auto rounded border border-border-modal/60">
|
||||
<table className="w-full text-[10px]">
|
||||
<thead className="sticky top-0 bg-bg-input text-text-muted">
|
||||
<tr>
|
||||
<th className="px-2 py-1 text-start">#</th>
|
||||
<th className="px-2 py-1 text-start">{t($ => $.properties.torrentPeerDownload)}</th>
|
||||
<th className="px-2 py-1 text-start">{t($ => $.properties.torrentPeerUpload)}</th>
|
||||
<th className="px-2 py-1 text-start">{t($ => $.properties.torrentPeerSeeder)}</th>
|
||||
<th className="px-2 py-1 text-start">{t($ => $.properties.torrentPeerAmChoking)}</th>
|
||||
<th className="px-2 py-1 text-start">{t($ => $.properties.torrentPeerChoking)}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{torrentPeerDiagnostics.peers.map((peer, index) => (
|
||||
<tr key={`${index}-${peer.downloadSpeed}-${peer.uploadSpeed}`} className="border-t border-border-modal/40 text-text-primary">
|
||||
<td className="px-2 py-1 font-mono">{index + 1}</td>
|
||||
<td className="px-2 py-1 font-mono">{formatPeerSpeed(peer.downloadSpeed)}</td>
|
||||
<td className="px-2 py-1 font-mono">{formatPeerSpeed(peer.uploadSpeed)}</td>
|
||||
<td className="px-2 py-1">{peer.seeder ? '✓' : '—'}</td>
|
||||
<td className="px-2 py-1">{peer.amChoking ? '✓' : '—'}</td>
|
||||
<td className="px-2 py-1">{peer.peerChoking ? '✓' : '—'}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{torrentPeerDiagnostics.truncated && (
|
||||
<p className="text-[11px] text-text-muted">
|
||||
{t($ => $.properties.torrentPeerShowing, {
|
||||
shown: torrentPeerDiagnostics.peers.length,
|
||||
total: torrentPeerDiagnostics.totalPeers
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<label className="text-xs text-text-muted text-right" htmlFor="torrent-trackers-properties">
|
||||
{t($ => $.properties.torrentTrackers)}
|
||||
</label>
|
||||
|
||||
@@ -245,6 +245,19 @@ 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',
|
||||
torrentPeerDiagnosticsRefresh: 'Refresh',
|
||||
torrentPeerDiagnosticsLoading: 'Loading peer diagnostics…',
|
||||
torrentPeerDiagnosticsUnavailable: 'Peer diagnostics are available while this Torrent is active.',
|
||||
torrentPeerDiagnosticsFailed: 'Could not read Torrent peer diagnostics.',
|
||||
torrentPeerDiagnosticsHint: 'Speeds and connection flags only are shown; peer IPs, ports, IDs, and bitfields are not retained.',
|
||||
torrentPeerCount: '{{total}} peers · {{seeders}} seeders',
|
||||
torrentPeerDownload: 'Download',
|
||||
torrentPeerUpload: 'Upload',
|
||||
torrentPeerSeeder: 'Seeder',
|
||||
torrentPeerAmChoking: 'Firelink choking',
|
||||
torrentPeerChoking: 'Peer choking',
|
||||
torrentPeerShowing: 'Showing {{shown}} of {{total}} peers.',
|
||||
seconds: 'seconds',
|
||||
torrentStopTimeout: 'Stop stalled Torrent after',
|
||||
torrentStopTimeoutHint: 'Aria2 stops this Torrent after this many consecutive seconds at 0 B/s. 0 disables the policy; changes apply when the Torrent starts or retries.',
|
||||
|
||||
@@ -245,6 +245,19 @@ const fa = {
|
||||
torrentPeerSpeedLimit: 'آستانه سرعت همتا',
|
||||
torrentMaxPeersInvalid: 'حداکثر همتاهای تورنت باید عددی صحیح بین ۰ و ۱۰۰۰ باشد',
|
||||
torrentPeerSpeedLimitInvalid: 'آستانه سرعت همتای تورنت باید بیشتر از صفر باشد',
|
||||
torrentPeerDiagnostics: 'اطلاعات همتاهای تورنت',
|
||||
torrentPeerDiagnosticsRefresh: 'تازهسازی',
|
||||
torrentPeerDiagnosticsLoading: 'در حال دریافت اطلاعات همتاها…',
|
||||
torrentPeerDiagnosticsUnavailable: 'اطلاعات همتاها هنگام فعال بودن تورنت در دسترس است.',
|
||||
torrentPeerDiagnosticsFailed: 'خواندن اطلاعات همتاهای تورنت ممکن نیست.',
|
||||
torrentPeerDiagnosticsHint: 'فقط سرعت و وضعیت اتصال نمایش داده میشود؛ IP، پورت، شناسه و بیتفیلد همتاها ذخیره نمیشود.',
|
||||
torrentPeerCount: '{{total}} همتا · {{seeders}} سید',
|
||||
torrentPeerDownload: 'دریافت',
|
||||
torrentPeerUpload: 'آپلود',
|
||||
torrentPeerSeeder: 'سید',
|
||||
torrentPeerAmChoking: 'محدودسازی از طرف Firelink',
|
||||
torrentPeerChoking: 'محدودسازی از طرف همتا',
|
||||
torrentPeerShowing: 'نمایش {{shown}} همتا از {{total}} همتا.',
|
||||
seconds: 'ثانیه',
|
||||
torrentStopTimeout: 'توقف تورنتِ بدون سرعت پس از',
|
||||
torrentStopTimeoutHint: 'آریا۲ پس از این تعداد ثانیه پیاپی با سرعت صفر، تورنت را متوقف میکند. ۰ این سیاست را غیرفعال میکند؛ تغییرات هنگام شروع یا تلاش مجدد اعمال میشوند.',
|
||||
|
||||
@@ -245,6 +245,19 @@ const he = {
|
||||
torrentPeerSpeedLimit: 'סף מהירות עמיתים',
|
||||
torrentMaxPeersInvalid: 'מספר העמיתים המרבי חייב להיות מספר שלם בין 0 ל-1000',
|
||||
torrentPeerSpeedLimitInvalid: 'סף מהירות העמיתים חייב להיות גדול מאפס',
|
||||
torrentPeerDiagnostics: 'אבחון עמיתי טורנט',
|
||||
torrentPeerDiagnosticsRefresh: 'רענון',
|
||||
torrentPeerDiagnosticsLoading: 'טוען אבחון עמיתים…',
|
||||
torrentPeerDiagnosticsUnavailable: 'אבחון עמיתים זמין כשהטורנט פעיל.',
|
||||
torrentPeerDiagnosticsFailed: 'לא ניתן לקרוא את אבחון עמיתי הטורנט.',
|
||||
torrentPeerDiagnosticsHint: 'מוצגים רק מהירויות ודגלי חיבור; כתובות IP, יציאות, מזהים ושדות ביטים אינם נשמרים.',
|
||||
torrentPeerCount: '{{total}} עמיתים · {{seeders}} משתפים',
|
||||
torrentPeerDownload: 'הורדה',
|
||||
torrentPeerUpload: 'העלאה',
|
||||
torrentPeerSeeder: 'משתף',
|
||||
torrentPeerAmChoking: 'Firelink מגביל',
|
||||
torrentPeerChoking: 'העמית מגביל',
|
||||
torrentPeerShowing: 'מוצגים {{shown}} מתוך {{total}} עמיתים.',
|
||||
seconds: 'שניות',
|
||||
torrentStopTimeout: 'עצירת טורנט תקוע לאחר',
|
||||
torrentStopTimeoutHint: 'Aria2 יעצור את הטורנט לאחר מספר זה של שניות רצופות במהירות 0 B/s. 0 משבית את המדיניות; השינוי חל כשהטורנט מתחיל או מנסה שוב.',
|
||||
|
||||
@@ -245,6 +245,19 @@ const ru = {
|
||||
torrentPeerSpeedLimit: 'Порог скорости пиров',
|
||||
torrentMaxPeersInvalid: 'Максимум пиров должен быть целым числом от 0 до 1000',
|
||||
torrentPeerSpeedLimitInvalid: 'Порог скорости пиров должен быть больше нуля',
|
||||
torrentPeerDiagnostics: 'Диагностика пиров торрента',
|
||||
torrentPeerDiagnosticsRefresh: 'Обновить',
|
||||
torrentPeerDiagnosticsLoading: 'Загрузка диагностики пиров…',
|
||||
torrentPeerDiagnosticsUnavailable: 'Диагностика пиров доступна, пока торрент активен.',
|
||||
torrentPeerDiagnosticsFailed: 'Не удалось получить диагностику пиров торрента.',
|
||||
torrentPeerDiagnosticsHint: 'Показываются только скорости и флаги соединения; IP-адреса, порты, идентификаторы и битовые поля не сохраняются.',
|
||||
torrentPeerCount: '{{total}} пиров · {{seeders}} сидеров',
|
||||
torrentPeerDownload: 'Загрузка',
|
||||
torrentPeerUpload: 'Отдача',
|
||||
torrentPeerSeeder: 'Сидер',
|
||||
torrentPeerAmChoking: 'Firelink ограничивает',
|
||||
torrentPeerChoking: 'Пир ограничивает',
|
||||
torrentPeerShowing: 'Показано {{shown}} из {{total}} пиров.',
|
||||
seconds: 'секунд',
|
||||
torrentStopTimeout: 'Останавливать неактивный торрент через',
|
||||
torrentStopTimeoutHint: 'Aria2 остановит этот торрент после указанного числа секунд подряд при скорости 0 Б/с. 0 отключает правило; изменения применяются при запуске или повторной попытке.',
|
||||
|
||||
@@ -245,6 +245,19 @@ const uk = {
|
||||
torrentPeerSpeedLimit: 'Поріг швидкості пірів',
|
||||
torrentMaxPeersInvalid: 'Максимум пірів має бути цілим числом від 0 до 1000',
|
||||
torrentPeerSpeedLimitInvalid: 'Поріг швидкості пірів має бути більшим за нуль',
|
||||
torrentPeerDiagnostics: 'Діагностика пірів торрента',
|
||||
torrentPeerDiagnosticsRefresh: 'Оновити',
|
||||
torrentPeerDiagnosticsLoading: 'Завантаження діагностики пірів…',
|
||||
torrentPeerDiagnosticsUnavailable: 'Діагностика пірів доступна, поки торрент активний.',
|
||||
torrentPeerDiagnosticsFailed: 'Не вдалося отримати діагностику пірів торрента.',
|
||||
torrentPeerDiagnosticsHint: 'Показуються лише швидкості та прапорці з’єднання; IP-адреси, порти, ідентифікатори й бітові поля не зберігаються.',
|
||||
torrentPeerCount: '{{total}} пірів · {{seeders}} сідів',
|
||||
torrentPeerDownload: 'Завантаження',
|
||||
torrentPeerUpload: 'Віддача',
|
||||
torrentPeerSeeder: 'Сідер',
|
||||
torrentPeerAmChoking: 'Firelink обмежує',
|
||||
torrentPeerChoking: 'Пір обмежує',
|
||||
torrentPeerShowing: 'Показано {{shown}} із {{total}} пірів.',
|
||||
seconds: 'секунд',
|
||||
torrentStopTimeout: 'Зупиняти торрент без швидкості через',
|
||||
torrentStopTimeoutHint: 'Aria2 зупинить цей торрент після вказаної кількості секунд поспіль зі швидкістю 0 Б/с. 0 вимикає правило; зміни застосовуються під час запуску або повторної спроби.',
|
||||
|
||||
@@ -245,6 +245,19 @@ const zhCN = {
|
||||
torrentPeerSpeedLimit: '对等节点速度阈值',
|
||||
torrentMaxPeersInvalid: 'Torrent 最大对等节点数必须是 0 到 1000 之间的整数',
|
||||
torrentPeerSpeedLimitInvalid: '对等节点速度阈值必须大于零',
|
||||
torrentPeerDiagnostics: 'Torrent 对等节点诊断',
|
||||
torrentPeerDiagnosticsRefresh: '刷新',
|
||||
torrentPeerDiagnosticsLoading: '正在加载对等节点诊断…',
|
||||
torrentPeerDiagnosticsUnavailable: 'Torrent 活跃时可查看对等节点诊断。',
|
||||
torrentPeerDiagnosticsFailed: '无法读取 Torrent 对等节点诊断。',
|
||||
torrentPeerDiagnosticsHint: '仅显示速度和连接状态;不会保留对等节点 IP、端口、ID 或位域。',
|
||||
torrentPeerCount: '{{total}} 个节点 · {{seeders}} 个做种节点',
|
||||
torrentPeerDownload: '下载',
|
||||
torrentPeerUpload: '上传',
|
||||
torrentPeerSeeder: '做种',
|
||||
torrentPeerAmChoking: 'Firelink 限制中',
|
||||
torrentPeerChoking: '对等节点限制中',
|
||||
torrentPeerShowing: '显示 {{total}} 个节点中的 {{shown}} 个。',
|
||||
seconds: '秒',
|
||||
torrentStopTimeout: '在此时间后停止无速度 Torrent',
|
||||
torrentStopTimeoutHint: 'Aria2 会在速度连续为 0 B/s 达到此秒数后停止该 Torrent。0 表示禁用;更改会在 Torrent 启动或重试时应用。',
|
||||
|
||||
@@ -19,6 +19,7 @@ import type { EnqueueAccepted } from './bindings/EnqueueAccepted';
|
||||
import type { PlatformInfo } from './bindings/PlatformInfo';
|
||||
import type { QueueConcurrencyConfig } from './bindings/QueueConcurrencyConfig';
|
||||
import type { TorrentMetadata } from './bindings/TorrentMetadata';
|
||||
import type { TorrentPeerDiagnostics } from './bindings/TorrentPeerDiagnostics';
|
||||
|
||||
type CommandMap = {
|
||||
fetch_metadata: {
|
||||
@@ -75,6 +76,7 @@ type CommandMap = {
|
||||
args: { id: string; max_peers: number | null; peer_speed_limit: string | null };
|
||||
result: void;
|
||||
};
|
||||
get_torrent_peers: { args: { id: string }; result: TorrentPeerDiagnostics };
|
||||
set_global_speed_limit: { args: { limit: string | null }; result: void };
|
||||
request_automation_permission: { args: undefined; result: void };
|
||||
check_automation_permission: { args: undefined; result: void };
|
||||
|
||||
Reference in New Issue
Block a user