fix(downloads): correct add window actions

This commit is contained in:
NimBold
2026-06-20 18:51:38 +03:30
parent 894c238bb7
commit 487e1fea43
13 changed files with 278 additions and 232 deletions
+6 -2
View File
@@ -80,10 +80,14 @@ export async function initDownloadListener() {
unlistenTray = await listen<string>('tray-action', (event) => {
const mainStore = useDownloadStore.getState();
if (event.payload === 'pause-all') {
const uniqueQueues = Array.from(new Set(mainStore.downloads.map(d => d.queueId)));
const uniqueQueues = Array.from(new Set(
mainStore.downloads.map(d => d.queueId).filter((id): id is string => Boolean(id))
));
uniqueQueues.forEach(qid => mainStore.pauseQueue(qid));
} else if (event.payload === 'resume-all') {
const uniqueQueues = Array.from(new Set(mainStore.downloads.map(d => d.queueId)));
const uniqueQueues = Array.from(new Set(
mainStore.downloads.map(d => d.queueId).filter((id): id is string => Boolean(id))
));
uniqueQueues.forEach(qid => mainStore.startQueue(qid));
}
});
+75
View File
@@ -91,4 +91,79 @@ describe('useDownloadStore', () => {
expect(calls.some(c => c[0] === 'enqueue_download')).toBe(true);
expect(useDownloadStore.getState().backendRegisteredIds.has('1')).toBe(true); // Re-registered by dispatchItem
});
it('adds to the list without assigning a queue or dispatching', async () => {
await useDownloadStore.getState().addDownload({
id: 'list-1',
url: 'https://example.com/list.bin',
fileName: 'list.bin',
category: 'Other',
dateAdded: ''
}, { type: 'add-to-list' });
const item = useDownloadStore.getState().downloads[0];
expect(item.status).toBe('ready');
expect(item.queueId).toBeUndefined();
expect(ipc.invokeCommand).not.toHaveBeenCalledWith('enqueue_download', expect.anything());
});
it('adds to the selected queue without dispatching', async () => {
await useDownloadStore.getState().addDownload({
id: 'queue-1',
url: 'https://example.com/queue.bin',
fileName: 'queue.bin',
category: 'Other',
dateAdded: ''
}, { type: 'add-to-queue', queueId: 'queue-b' });
const item = useDownloadStore.getState().downloads[0];
expect(item.status).toBe('queued');
expect(item.queueId).toBe('queue-b');
expect(useDownloadStore.getState().pendingOrder).toContain('queue-1');
expect(ipc.invokeCommand).not.toHaveBeenCalledWith('enqueue_download', expect.anything());
});
it('starts immediately without assigning a user queue', async () => {
vi.mocked(ipc.invokeCommand).mockImplementation(async (cmd: string) => {
if (cmd === 'get_pending_order') return ['start-1'];
return undefined;
});
await useDownloadStore.getState().addDownload({
id: 'start-1',
url: 'https://example.com/start.bin',
fileName: 'start.bin',
category: 'Other',
dateAdded: ''
}, { type: 'start-now' });
const item = useDownloadStore.getState().downloads[0];
expect(item.queueId).toBeUndefined();
expect(item.hasBeenDispatched).toBe(true);
expect(ipc.invokeCommand).toHaveBeenCalledWith(
'enqueue_download',
expect.objectContaining({
item: expect.objectContaining({ id: 'start-1' })
})
);
});
it('preserves extension request headers and cookies for the Add modal', () => {
useDownloadStore.getState().handleExtensionDownload({
urls: ['https://example.com/file.bin'],
referer: 'https://example.com/page',
silent: false,
filename: 'file.bin',
headers: 'X-Test: value',
cookies: 'session=secret'
});
const state = useDownloadStore.getState();
expect(state.isAddModalOpen).toBe(true);
expect(state.pendingAddUrls).toBe('https://example.com/file.bin');
expect(state.pendingAddReferer).toBe('https://example.com/page');
expect(state.pendingAddFilename).toBe('file.bin');
expect(state.pendingAddHeaders).toBe('X-Test: value');
expect(state.pendingAddCookies).toBe('session=secret');
});
});
+49 -13
View File
@@ -147,6 +147,11 @@ export const MAIN_QUEUE_ID = '00000000-0000-0000-0000-000000000001';
export type { DownloadItem, Queue };
export type ExtensionDownloadRequest = ExtensionDownload;
export type AddDownloadAction =
| { type: 'start-now' }
| { type: 'add-to-list' }
| { type: 'add-to-queue'; queueId: string };
export type DownloadDraft = Omit<DownloadItem, 'status' | 'queueId' | 'hasBeenDispatched'>;
export type DeleteModalState = {
isOpen: boolean;
@@ -170,15 +175,23 @@ interface DownloadState {
pendingAddUrls: string;
pendingAddReferer: string;
pendingAddFilename: string;
pendingAddHeaders: string;
pendingAddCookies: string;
selectedPropertiesDownloadId: string | null;
toggleAddModal: (isOpen: boolean) => void;
openAddModalWithUrls: (urls: string, referer?: string | null, filename?: string | null) => void;
openAddModalWithUrls: (
urls: string,
referer?: string | null,
filename?: string | null,
headers?: string | null,
cookies?: string | null
) => void;
handleExtensionDownload: (request: ExtensionDownloadRequest) => void;
deleteModalState: DeleteModalState;
openDeleteModal: (downloadId?: string) => void;
closeDeleteModal: () => void;
setSelectedPropertiesDownloadId: (id: string | null) => void;
addDownload: (item: DownloadItem) => Promise<void>;
addDownload: (item: DownloadDraft, action: AddDownloadAction) => Promise<void>;
updateDownload: (id: string, updates: Partial<DownloadItem>) => void;
removeDownload: (id: string, deleteFile?: boolean) => Promise<void>;
redownload: (id: string) => Promise<void>;
@@ -238,6 +251,8 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
pendingAddUrls: '',
pendingAddReferer: '',
pendingAddFilename: '',
pendingAddHeaders: '',
pendingAddCookies: '',
selectedPropertiesDownloadId: null,
isParsing: false,
activeMetadata: null,
@@ -250,16 +265,20 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
isAddModalOpen: isOpen,
pendingAddUrls: '',
pendingAddReferer: '',
pendingAddFilename: ''
pendingAddFilename: '',
pendingAddHeaders: '',
pendingAddCookies: ''
}),
openAddModalWithUrls: (urls, referer, filename) => set((state) => {
openAddModalWithUrls: (urls, referer, filename, headers, cookies) => set((state) => {
const existingUrls = state.isAddModalOpen && state.pendingAddUrls ? state.pendingAddUrls : '';
const mergedUrls = existingUrls ? `${existingUrls}\n${urls}` : urls;
return {
isAddModalOpen: true,
pendingAddUrls: mergedUrls,
pendingAddReferer: referer?.trim() || state.pendingAddReferer || '',
pendingAddFilename: filename?.trim() || state.pendingAddFilename || ''
pendingAddFilename: filename?.trim() || state.pendingAddFilename || '',
pendingAddHeaders: headers?.trim() || state.pendingAddHeaders || '',
pendingAddCookies: cookies?.trim() || state.pendingAddCookies || ''
};
}),
handleExtensionDownload: (request) => {
@@ -269,7 +288,9 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
get().openAddModalWithUrls(
urls.join('\n'),
request.referer,
urls.length === 1 ? request.filename : null
urls.length === 1 ? request.filename : null,
request.headers,
request.cookies
);
},
setSelectedPropertiesDownloadId: (id) => set({ selectedPropertiesDownloadId: id }),
@@ -291,23 +312,31 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
info(`Media metadata parsing failed for ${url}: ${e}`);
}
},
addDownload: async (item) => {
info(`Download ${item.id} added to queue`);
addDownload: async (item, action) => {
const settings = useSettingsStore.getState();
const destPath = effectiveDestinationForItem(item, settings);
const ownedItem = { ...item, destination: destPath, hasBeenDispatched: false };
const ownedItem: DownloadItem = {
...item,
destination: destPath,
status: action.type === 'add-to-queue' ? 'queued' : 'ready',
queueId: action.type === 'add-to-queue' ? action.queueId : undefined,
hasBeenDispatched: false
};
set((state) => ({ downloads: [...state.downloads, ownedItem] }));
if (item.status === 'queued') {
if (action.type === 'add-to-queue') {
const order = useDownloadStore.getState().pendingOrder;
if (!order.includes(item.id)) {
useDownloadStore.getState().setPendingOrder([...order, item.id]);
}
} else {
// Immediate dispatch (e.g., "Start immediately" from UI where status isn't just 'queued')
info(`Download ${item.id} added to queue ${action.queueId}`);
} else if (action.type === 'start-now') {
if (await dispatchItem(item.id)) {
get().updateDownload(item.id, { hasBeenDispatched: true });
}
info(`Download ${item.id} started`);
} else {
info(`Download ${item.id} added to list`);
}
},
applyProperties: async (id, updates) => {
@@ -319,7 +348,7 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
throw new Error("Cannot change properties while transfer is active. Pause it first.");
}
if (item.status === 'completed' || item.status === 'failed') {
if (item.status === 'ready' || item.status === 'completed' || item.status === 'failed') {
state.updateDownload(id, updates);
return;
}
@@ -470,6 +499,13 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
if (!targetItem) return;
try {
if (targetItem.status === 'ready') {
if (await dispatchItem(id)) {
get().updateDownload(id, { hasBeenDispatched: true });
}
return;
}
const resumedExisting = await invoke('resume_download', { id });
if (resumedExisting) {
return;