mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-06 09:30:29 +00:00
feat(downloads): expose active Aria2 connections
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type DownloadProgressEvent = { id: string, fraction: number, speed: string, eta: string, size: string | null, size_is_final: boolean, downloaded_bytes?: number, total_bytes?: number, total_is_estimate?: boolean, };
|
||||
export type DownloadProgressEvent = { id: string, fraction: number, speed: string, eta: string, size: string | null, size_is_final: boolean, downloaded_bytes?: number, total_bytes?: number, total_is_estimate?: boolean, active_connections?: number, requested_connections?: number, };
|
||||
|
||||
@@ -207,6 +207,32 @@ export const PropertiesModal = () => {
|
||||
|
||||
const identityLocked = getIdentityLocked(item.status);
|
||||
const transferLocked = getTransferLocked(item.status);
|
||||
const configuredConnections = resolveDownloadConnections(item.connections, perServerConnections);
|
||||
const observedConnectionTotal = Math.max(
|
||||
1,
|
||||
liveProgress?.requested_connections ?? configuredConnections
|
||||
);
|
||||
const observedActiveConnections = liveProgress?.active_connections;
|
||||
const connectionTelemetryActive = item.status === 'downloading' ||
|
||||
item.status === 'processing' ||
|
||||
item.status === 'retrying';
|
||||
const connectionStatus = (() => {
|
||||
if (item.isMedia) return t($ => $.properties.connectionsUnavailable);
|
||||
if (!connectionTelemetryActive) return String(configuredConnections);
|
||||
if (typeof observedActiveConnections === 'number') {
|
||||
return t($ => $.properties.connectionCount, {
|
||||
active: observedActiveConnections,
|
||||
total: observedConnectionTotal,
|
||||
});
|
||||
}
|
||||
if (item.status === 'downloading') {
|
||||
return t($ => $.properties.connectionCountUnknown, { total: observedConnectionTotal });
|
||||
}
|
||||
return t($ => $.properties.connectionCount, {
|
||||
active: 0,
|
||||
total: observedConnectionTotal,
|
||||
});
|
||||
})();
|
||||
const displayedFraction = item.status === 'completed'
|
||||
? 1
|
||||
: liveProgress?.fraction ?? item.fraction ?? 0;
|
||||
@@ -298,7 +324,7 @@ export const PropertiesModal = () => {
|
||||
<div className="flex gap-1.5 min-w-0"><span className="text-text-muted font-medium w-[40px] shrink-0">{t($ => $.properties.speed)}</span><span className="text-text-secondary truncate">{displayedSpeed}</span></div>
|
||||
<div className="flex gap-1.5 min-w-0"><span className="text-text-muted font-medium w-[30px] shrink-0">{t($ => $.properties.eta)}</span><span className="text-text-secondary truncate">{displayedEta}</span></div>
|
||||
|
||||
<div className="flex gap-1.5 min-w-0"><span className="text-text-muted font-medium w-[90px] shrink-0">{t($ => $.properties.connections)}</span><span className="text-text-secondary truncate" title={item.connections !== undefined ? t($ => $.properties.savedTooltip) : t($ => $.properties.defaultTooltip)}>{resolveDownloadConnections(item.connections, perServerConnections)}{item.connections !== undefined ? t($ => $.properties.saved) : t($ => $.properties.defaultValue)}</span></div>
|
||||
<div className="flex gap-1.5 min-w-0"><span className="text-text-muted font-medium w-[90px] shrink-0">{t($ => $.properties.connections)}</span><span className="text-text-secondary truncate" title={item.connections !== undefined ? t($ => $.properties.savedTooltip) : t($ => $.properties.defaultTooltip)}><bdi>{connectionStatus}</bdi>{item.connections !== undefined ? t($ => $.properties.saved) : t($ => $.properties.defaultValue)}</span></div>
|
||||
<div className="flex gap-1.5 min-w-0"><span className="text-text-muted font-medium w-[60px] shrink-0">{t($ => $.properties.speedCap)}</span><span className="text-text-secondary truncate">{item.speedLimit || '-'}</span></div>
|
||||
<div className="flex gap-1.5 min-w-0"><span className="text-text-muted font-medium w-[55px] shrink-0">{t($ => $.properties.category)}</span><span className="text-text-secondary truncate">{categoryLabel(item.category)}</span></div>
|
||||
<div className="flex gap-1.5"><span className="text-text-muted font-medium w-[50px]">{t($ => $.properties.lastTry)}</span><span className="text-text-secondary truncate">{formatLastTry(item.lastTry)}</span></div>
|
||||
@@ -352,6 +378,7 @@ export const PropertiesModal = () => {
|
||||
<div className="flex items-center gap-2">
|
||||
<input type="number" value={connections} min={1} max={16} onChange={e=>{ setConnections(Number(e.target.value)); setConnectionsDirty(true); }} disabled={transferLocked} className="w-16 bg-bg-input border border-border-modal rounded-lg px-2.5 py-1.5 text-xs text-text-primary focus:outline-none focus:border-accent disabled:opacity-50" />
|
||||
<span className="text-xs text-text-muted">{t($ => $.properties.perFile)}</span>
|
||||
<span className="text-xs text-text-secondary font-mono" aria-live="polite"><bdi>{connectionStatus}</bdi></span>
|
||||
{!transferLocked && item.connections !== undefined && item.connections !== perServerConnections && (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -212,6 +212,9 @@ const common = {
|
||||
speed: 'Speed',
|
||||
eta: 'ETA',
|
||||
connections: 'Connections',
|
||||
connectionCount: '{{active}}/{{total}}',
|
||||
connectionCountUnknown: '—/{{total}}',
|
||||
connectionsUnavailable: '—',
|
||||
speedCap: 'Speed cap',
|
||||
category: 'Category',
|
||||
lastTry: 'Last try',
|
||||
|
||||
@@ -212,6 +212,9 @@ const fa = {
|
||||
speed: 'سرعت',
|
||||
eta: 'زمان باقیمانده',
|
||||
connections: 'اتصالات',
|
||||
connectionCount: '{{active}}/{{total}} فعال',
|
||||
connectionCountUnknown: '—/{{total}} فعال',
|
||||
connectionsUnavailable: '—',
|
||||
speedCap: 'سقف سرعت',
|
||||
category: 'دسته',
|
||||
lastTry: 'آخرین تلاش',
|
||||
|
||||
@@ -212,6 +212,9 @@ const he = {
|
||||
speed: 'מהירות',
|
||||
eta: 'זמן נותר',
|
||||
connections: 'חיבורים',
|
||||
connectionCount: '{{active}}/{{total}} פעילות',
|
||||
connectionCountUnknown: '—/{{total}} פעילות',
|
||||
connectionsUnavailable: '—',
|
||||
speedCap: 'הגבלת מהירות',
|
||||
category: 'קטגוריה',
|
||||
lastTry: 'ניסיון אחרון',
|
||||
|
||||
@@ -212,6 +212,9 @@ const ru = {
|
||||
speed: 'Скорость',
|
||||
eta: 'Осталось',
|
||||
connections: 'Соединения',
|
||||
connectionCount: '{{active}}/{{total}} активных',
|
||||
connectionCountUnknown: '—/{{total}} активных',
|
||||
connectionsUnavailable: '—',
|
||||
speedCap: 'Ограничение скорости',
|
||||
category: 'Категория',
|
||||
lastTry: 'Последняя попытка',
|
||||
|
||||
@@ -212,6 +212,9 @@ const uk = {
|
||||
speed: 'Швидкість',
|
||||
eta: 'Залишилось',
|
||||
connections: 'З\'єднання',
|
||||
connectionCount: '{{active}}/{{total}} активних',
|
||||
connectionCountUnknown: '—/{{total}} активних',
|
||||
connectionsUnavailable: '—',
|
||||
speedCap: 'Обмеження швидкості',
|
||||
category: 'Категорія',
|
||||
lastTry: 'Остання спроба',
|
||||
|
||||
@@ -212,6 +212,9 @@ const zhCN = {
|
||||
speed: '速度',
|
||||
eta: '剩余时间',
|
||||
connections: '连接数',
|
||||
connectionCount: '{{active}}/{{total}} 个连接',
|
||||
connectionCountUnknown: '—/{{total}} 个连接',
|
||||
connectionsUnavailable: '—',
|
||||
speedCap: '速度上限',
|
||||
category: '类别',
|
||||
lastTry: '上次尝试',
|
||||
|
||||
@@ -90,6 +90,40 @@ describe('useDownloadProgressStore', () => {
|
||||
release();
|
||||
});
|
||||
|
||||
it('keeps Aria2 connection telemetry from the live progress event', async () => {
|
||||
const handlers: Record<string, (event: any) => void> = {};
|
||||
vi.mocked(ipc.listenEvent).mockImplementation((event, handler) => {
|
||||
handlers[event] = handler as (event: any) => void;
|
||||
return Promise.resolve(vi.fn());
|
||||
});
|
||||
useDownloadStore.setState({
|
||||
downloads: [{
|
||||
id: 'connection-telemetry',
|
||||
url: 'https://github.com/example/release.zip',
|
||||
fileName: 'release.zip',
|
||||
status: 'downloading',
|
||||
category: 'Compressed',
|
||||
dateAdded: ''
|
||||
}]
|
||||
});
|
||||
|
||||
const release = await initDownloadListener();
|
||||
handlers['download-progress']({ payload: {
|
||||
id: 'connection-telemetry',
|
||||
fraction: 0.2,
|
||||
speed: '3 MB/s',
|
||||
eta: '10s',
|
||||
size: '38 MB',
|
||||
size_is_final: false,
|
||||
active_connections: 16,
|
||||
requested_connections: 16
|
||||
} });
|
||||
|
||||
expect(useDownloadProgressStore.getState().progressMap['connection-telemetry'])
|
||||
.toMatchObject({ active_connections: 16, requested_connections: 16 });
|
||||
release();
|
||||
});
|
||||
|
||||
it('clears progress when events arrive after a download row was removed', async () => {
|
||||
const handlers: Record<string, (event: any) => void> = {};
|
||||
vi.mocked(ipc.listenEvent).mockImplementation((event, handler) => {
|
||||
|
||||
Reference in New Issue
Block a user