fix: harden scheduler, permissions, and download safety

- Implement scheduler hydration barrier to prevent premature triggers
- Track scheduler exact runs using keys to avoid false stops
- Use 'System Events' for accurate macOS automation permissions
- Prevent system-sleep via proper idle assertions
- Ensure download pauses use channel acknowledgements (PauseWithAck)
- Require Firelink ownership before replacing files in add/conflict UI
- Retain partial download assets when removing entries without deletion
- Clear progress state in store when downloads complete or pause to reduce churn
- Handle empty/invalid queue selections gracefully
This commit is contained in:
NimBold
2026-06-22 18:05:04 +03:30
parent 6b3753949b
commit 7c835b022f
17 changed files with 529 additions and 181 deletions
+48
View File
@@ -1,5 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useDownloadStore } from './useDownloadStore';
import { useSettingsStore } from './useSettingsStore';
import * as ipc from '../ipc';
vi.mock('../ipc', () => ({
@@ -172,6 +173,26 @@ describe('useDownloadStore', () => {
);
});
it('reports a rejected immediate start instead of claiming success', async () => {
vi.mocked(ipc.invokeCommand).mockImplementation(async (command: string) => {
if (command === 'enqueue_download') {
throw new Error('backend unavailable');
}
return undefined;
});
const added = await useDownloadStore.getState().addDownload({
id: 'rejected-start',
url: 'https://example.com/rejected.bin',
fileName: 'rejected.bin',
category: 'Other',
dateAdded: ''
}, { type: 'start-now' });
expect(added).toBe(false);
expect(useDownloadStore.getState().downloads[0].status).toBe('failed');
});
it('redownloads fallback media without requiring a format selector', async () => {
useDownloadStore.setState({
downloads: [{
@@ -280,6 +301,33 @@ describe('useDownloadStore', () => {
expect(useDownloadStore.getState().downloads.find(item => item.id === 'done')?.queueId).toBe('old');
});
it('disables scheduler when its last selected queue is deleted', async () => {
const originalSettings = useSettingsStore.getState();
const setScheduler = vi.fn();
vi.mocked(useSettingsStore.getState).mockReturnValue({
scheduler: {
enabled: true,
selectedQueueIds: ['queue-a']
},
setScheduler
} as any);
useDownloadStore.setState({
queues: [
{ id: '00000000-0000-0000-0000-000000000001', name: 'Main Queue', isMain: true },
{ id: 'queue-a', name: 'Scheduled', isMain: false }
],
downloads: []
});
await useDownloadStore.getState().removeQueue('queue-a');
expect(setScheduler).toHaveBeenCalledWith(expect.objectContaining({
enabled: false,
selectedQueueIds: []
}));
vi.mocked(useSettingsStore.getState).mockReturnValue(originalSettings);
});
it('retains the UI item when backend removal fails', async () => {
useDownloadStore.setState({
downloads: [