feat(downloads): add live limits clipboard capture and byte progress

This commit is contained in:
NimBold
2026-07-15 20:35:05 +03:30
parent 9917f29743
commit 1d197432b2
18 changed files with 536 additions and 25 deletions
+9
View File
@@ -55,6 +55,15 @@ const startDownloadListeners = async () => {
if (shouldUpdateSize && current.size !== payload.size) {
updates.size = payload.size!;
}
if (payload.downloaded_bytes !== null && payload.downloaded_bytes !== undefined) {
updates.downloadedBytes = payload.downloaded_bytes;
}
if (payload.total_bytes !== null && payload.total_bytes !== undefined) {
updates.totalBytes = payload.total_bytes;
}
if (payload.downloaded_bytes !== null && payload.downloaded_bytes !== undefined) {
updates.totalIsEstimate = payload.total_is_estimate;
}
if (Object.keys(updates).length > 0) {
mainStore.updateDownload(payload.id, updates);
}
+32 -1
View File
@@ -510,7 +510,7 @@ describe('useDownloadStore', () => {
);
});
it('uses the global speed limit only when an item has no explicit speed override', async () => {
it('does not copy the global speed limit into a normal download task', async () => {
const defaultSettings = useSettingsStore.getState();
vi.mocked(useSettingsStore.getState).mockReturnValue({
...defaultSettings,
@@ -534,6 +534,37 @@ describe('useDownloadStore', () => {
expect.objectContaining({
item: expect.objectContaining({
id: 'inherits-global',
speed_limit: null
})
})
);
});
it('passes the global speed limit to a new media process when it has no item override', async () => {
const defaultSettings = useSettingsStore.getState();
vi.mocked(useSettingsStore.getState).mockReturnValue({
...defaultSettings,
globalSpeedLimit: '2M'
});
vi.mocked(ipc.invokeCommand).mockImplementation(async (cmd: string) => {
if (cmd === 'get_pending_order') return ['inherits-global-media'];
return undefined;
});
await useDownloadStore.getState().addDownload({
id: 'inherits-global-media',
url: 'https://www.youtube.com/watch?v=example',
fileName: 'media.mp4',
category: 'Movies',
dateAdded: '',
isMedia: true
}, { type: 'start-now' });
expect(ipc.invokeCommand).toHaveBeenCalledWith(
'enqueue_download',
expect.objectContaining({
item: expect.objectContaining({
id: 'inherits-global-media',
speed_limit: '2M'
})
})
+16 -3
View File
@@ -134,11 +134,21 @@ const stripCookieHeaders = (value: string | null | undefined): string =>
.join('\n')
.trim();
const speedLimitForDispatch = (itemSpeedLimit: string | undefined, globalSpeedLimit: string): string | null => {
const explicitSpeedLimitForDispatch = (itemSpeedLimit: string | undefined): string | null => {
const explicitLimit = itemSpeedLimit?.trim();
if (explicitLimit) {
return normalizeSpeedLimitForBackend(explicitLimit) || (explicitLimit === '0' ? '0' : null);
}
return null;
};
const speedLimitForDispatch = (
itemSpeedLimit: string | undefined,
globalSpeedLimit: string,
isMedia: boolean | undefined
): string | null => {
const explicitLimit = explicitSpeedLimitForDispatch(itemSpeedLimit);
if (explicitLimit !== null || !isMedia) return explicitLimit;
return normalizeSpeedLimitForBackend(globalSpeedLimit);
};
@@ -186,7 +196,7 @@ export async function dispatchItem(id: string): Promise<boolean> {
destination,
filename: item.fileName,
connections: item.connections || settings.perServerConnections || null,
speed_limit: speedLimitForDispatch(item.speedLimit, settings.globalSpeedLimit),
speed_limit: speedLimitForDispatch(item.speedLimit, settings.globalSpeedLimit, item.isMedia),
username: item.username || (login ? login.username : null),
password: item.password || keychainPassword,
headers: item.headers || null,
@@ -842,6 +852,9 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
fraction: 0,
speed: '-',
eta: '-',
downloadedBytes: undefined,
totalBytes: undefined,
totalIsEstimate: undefined,
hasBeenDispatched: false,
dateAdded: new Date().toISOString()
});
@@ -1159,7 +1172,7 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
destination: destPath,
filename: item.fileName,
connections: item.connections || settings.perServerConnections || null,
speed_limit: speedLimitForDispatch(item.speedLimit, settings.globalSpeedLimit),
speed_limit: speedLimitForDispatch(item.speedLimit, settings.globalSpeedLimit, item.isMedia),
username: item.username || (login ? login.username : null),
password: item.password || keychainPassword,
headers: item.headers || null,
+9
View File
@@ -143,6 +143,7 @@ export interface SettingsState {
maxAutomaticRetries: number;
showNotifications: boolean;
playCompletionSound: boolean;
autoAddClipboardLinks: boolean;
appFontSize: AppFontSize;
listRowDensity: ListRowDensity;
showDockBadge: boolean;
@@ -186,6 +187,7 @@ export interface SettingsState {
setMaxAutomaticRetries: (count: number) => void;
setShowNotifications: (show: boolean) => void;
setPlayCompletionSound: (play: boolean) => void;
setAutoAddClipboardLinks: (enabled: boolean) => void;
setAppFontSize: (size: AppFontSize) => void;
setListRowDensity: (density: ListRowDensity) => void;
setShowDockBadge: (show: boolean) => void;
@@ -250,6 +252,7 @@ export const useSettingsStore = create<SettingsState>()(
maxAutomaticRetries: 3,
showNotifications: true,
playCompletionSound: false,
autoAddClipboardLinks: false,
appFontSize: 'standard',
listRowDensity: 'standard',
showDockBadge: true,
@@ -319,6 +322,7 @@ export const useSettingsStore = create<SettingsState>()(
}),
setShowNotifications: (showNotifications) => set({ showNotifications }),
setPlayCompletionSound: (playCompletionSound) => set({ playCompletionSound }),
setAutoAddClipboardLinks: (autoAddClipboardLinks) => set({ autoAddClipboardLinks }),
setAppFontSize: (appFontSize) => set({ appFontSize }),
setListRowDensity: (listRowDensity) => set({ listRowDensity }),
setShowDockBadge: (showDockBadge) => {
@@ -477,6 +481,7 @@ export const useSettingsStore = create<SettingsState>()(
maxAutomaticRetries: state.maxAutomaticRetries,
showNotifications: state.showNotifications,
playCompletionSound: state.playCompletionSound,
autoAddClipboardLinks: state.autoAddClipboardLinks,
appFontSize: state.appFontSize,
listRowDensity: state.listRowDensity,
showDockBadge: state.showDockBadge,
@@ -525,6 +530,10 @@ export const useSettingsStore = create<SettingsState>()(
: currentState.activeSettingsTab,
showNotifications: persistedBoolean(persisted.showNotifications, currentState.showNotifications),
playCompletionSound: persistedBoolean(persisted.playCompletionSound, currentState.playCompletionSound),
autoAddClipboardLinks: persistedBoolean(
persisted.autoAddClipboardLinks,
currentState.autoAddClipboardLinks
),
showDockBadge: persistedBoolean(persisted.showDockBadge, currentState.showDockBadge),
showMenuBarIcon: persistedBoolean(persisted.showMenuBarIcon, currentState.showMenuBarIcon),
askWhereToSaveEachFile: persistedBoolean(