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
+16 -7
View File
@@ -125,11 +125,6 @@ const syncSystemIntegrations = () => {
const settings = useSettingsStore.getState();
const activeCount = useDownloadStore.getState().downloads.filter(d => d.status === 'downloading').length;
invoke('update_dock_badge', { count: settings.showDockBadge ? activeCount : 0 }).catch(() => {});
if (settings.preventsSleepWhileDownloading) {
invoke('set_prevent_sleep', { prevent: activeCount > 0 }).catch(() => {});
} else {
invoke('set_prevent_sleep', { prevent: false }).catch(() => {});
}
};
const effectiveDestinationForItem = async (
@@ -198,7 +193,7 @@ interface DownloadState {
openDeleteModal: (downloadIds?: string | string[]) => void;
closeDeleteModal: () => void;
setSelectedPropertiesDownloadId: (id: string | null) => void;
addDownload: (item: DownloadDraft, action: AddDownloadAction) => Promise<void>;
addDownload: (item: DownloadDraft, action: AddDownloadAction) => Promise<boolean>;
updateDownload: (id: string, updates: Partial<DownloadItem>) => void;
removeDownload: (id: string, deleteFile?: boolean) => Promise<void>;
redownload: (id: string) => Promise<void>;
@@ -335,12 +330,16 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
if (action.type === 'add-to-queue') {
info(`Download ${item.id} added to queue ${action.queueId}`);
return true;
} else if (action.type === 'start-now') {
if (await dispatchItem(item.id)) {
get().updateDownload(item.id, { hasBeenDispatched: true });
info(`Download ${item.id} started`);
return true;
}
info(`Download ${item.id} started`);
return false;
}
return false;
},
applyProperties: async (id, updates) => {
const state = get();
@@ -676,6 +675,15 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
d.queueId === id ? { ...d, queueId: MAIN_QUEUE_ID } : d
)
}));
const settings = useSettingsStore.getState();
if (settings.scheduler.selectedQueueIds.includes(id)) {
const selectedQueueIds = settings.scheduler.selectedQueueIds.filter(queueId => queueId !== id);
settings.setScheduler({
...settings.scheduler,
enabled: selectedQueueIds.length > 0 ? settings.scheduler.enabled : false,
selectedQueueIds
});
}
},
initDB: async () => {
try {
@@ -764,6 +772,7 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
}
} catch (e) {
console.error("Failed to init DB", e);
throw e;
}
}
}));