mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-07 01:44:01 +00:00
fix(downloads): harden add modal and retry state
Route explicit no-limit speed overrides without inheriting the global cap. Preserve dotted media titles when switching formats and drain retry gid completions through remember_gid.
This commit is contained in:
@@ -54,6 +54,29 @@ vi.mock('./useSettingsStore', () => ({
|
||||
describe('useDownloadStore', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(useSettingsStore.getState).mockReturnValue({
|
||||
proxyMode: 'none',
|
||||
siteLogins: [],
|
||||
globalSpeedLimit: '',
|
||||
speedLimitPresetValues: [1, 5, 10],
|
||||
logsEnabled: false,
|
||||
perServerConnections: 16,
|
||||
customUserAgent: '',
|
||||
maxAutomaticRetries: 3,
|
||||
mediaCookieSource: 'none',
|
||||
baseDownloadFolder: '~/Downloads',
|
||||
categorySubfoldersEnabled: true,
|
||||
categorySubfolders: {
|
||||
Musics: 'Musics',
|
||||
Movies: 'Movies',
|
||||
Compressed: 'Compressed',
|
||||
Documents: 'Documents',
|
||||
Pictures: 'Pictures',
|
||||
Applications: 'Applications',
|
||||
Other: 'Other',
|
||||
},
|
||||
categoryDirectoryOverrides: {},
|
||||
} as unknown as ReturnType<typeof useSettingsStore.getState>);
|
||||
useDownloadStore.setState({
|
||||
downloads: [],
|
||||
backendRegisteredIds: new Set(),
|
||||
@@ -253,6 +276,67 @@ describe('useDownloadStore', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('does not replace an explicit no-limit item speed with the global speed limit', 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 ['uncapped'];
|
||||
return undefined;
|
||||
});
|
||||
|
||||
await useDownloadStore.getState().addDownload({
|
||||
id: 'uncapped',
|
||||
url: 'https://example.com/uncapped.bin',
|
||||
fileName: 'uncapped.bin',
|
||||
category: 'Other',
|
||||
dateAdded: '',
|
||||
speedLimit: '0'
|
||||
}, { type: 'start-now' });
|
||||
|
||||
expect(ipc.invokeCommand).toHaveBeenCalledWith(
|
||||
'enqueue_download',
|
||||
expect.objectContaining({
|
||||
item: expect.objectContaining({
|
||||
id: 'uncapped',
|
||||
speed_limit: '0'
|
||||
})
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('uses the global speed limit only when an item has no explicit speed 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'];
|
||||
return undefined;
|
||||
});
|
||||
|
||||
await useDownloadStore.getState().addDownload({
|
||||
id: 'inherits-global',
|
||||
url: 'https://example.com/inherits-global.bin',
|
||||
fileName: 'inherits-global.bin',
|
||||
category: 'Other',
|
||||
dateAdded: ''
|
||||
}, { type: 'start-now' });
|
||||
|
||||
expect(ipc.invokeCommand).toHaveBeenCalledWith(
|
||||
'enqueue_download',
|
||||
expect.objectContaining({
|
||||
item: expect.objectContaining({
|
||||
id: 'inherits-global',
|
||||
speed_limit: '2M'
|
||||
})
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('reports a rejected immediate start instead of claiming success', async () => {
|
||||
vi.mocked(ipc.invokeCommand).mockImplementation(async (command: string) => {
|
||||
if (command === 'enqueue_download') {
|
||||
|
||||
@@ -20,6 +20,14 @@ const backendDispatchPromises = new Map<string, Promise<boolean>>();
|
||||
const errorMessage = (error: unknown): string =>
|
||||
error instanceof Error ? error.message : String(error);
|
||||
|
||||
const speedLimitForDispatch = (itemSpeedLimit: string | undefined, globalSpeedLimit: string): string | null => {
|
||||
const explicitLimit = itemSpeedLimit?.trim();
|
||||
if (explicitLimit) {
|
||||
return normalizeSpeedLimitForBackend(explicitLimit) || (explicitLimit === '0' ? '0' : null);
|
||||
}
|
||||
return normalizeSpeedLimitForBackend(globalSpeedLimit);
|
||||
};
|
||||
|
||||
export async function dispatchItem(id: string): Promise<boolean> {
|
||||
if (backendDispatchPromises.has(id)) return backendDispatchPromises.get(id)!;
|
||||
|
||||
@@ -50,7 +58,7 @@ export async function dispatchItem(id: string): Promise<boolean> {
|
||||
destination,
|
||||
filename: item.fileName,
|
||||
connections: item.connections || settings.perServerConnections || null,
|
||||
speed_limit: item.speedLimit || normalizeSpeedLimitForBackend(settings.globalSpeedLimit),
|
||||
speed_limit: speedLimitForDispatch(item.speedLimit, settings.globalSpeedLimit),
|
||||
username: item.username || (login ? login.username : null),
|
||||
password: item.password || keychainPassword,
|
||||
headers: item.headers || null,
|
||||
@@ -860,7 +868,7 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
|
||||
destination: destPath,
|
||||
filename: item.fileName,
|
||||
connections: item.connections || settings.perServerConnections || null,
|
||||
speed_limit: item.speedLimit || normalizeSpeedLimitForBackend(settings.globalSpeedLimit),
|
||||
speed_limit: speedLimitForDispatch(item.speedLimit, settings.globalSpeedLimit),
|
||||
username: item.username || (login ? login.username : null),
|
||||
password: item.password || keychainPassword,
|
||||
headers: item.headers || null,
|
||||
|
||||
Reference in New Issue
Block a user