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
+24 -12
View File
@@ -414,9 +414,20 @@ export const AddDownloadsModal = () => {
});
} catch (e) {}
if (fileExistsInStore || fileExistsOnDisk) {
newConflicts.push({ id: i.toString(), fileName: finalFile, reason: { type: 'file', msg: 'File exists at destination' }, resolution: 'rename' });
}
if (fileExistsInStore || fileExistsOnDisk) {
newConflicts.push({
id: i.toString(),
fileName: finalFile,
reason: {
type: 'file',
msg: fileExistsInStore
? 'Existing Firelink download uses this destination'
: 'File exists on disk; rename or skip to avoid deleting unrelated data'
},
resolution: 'rename',
replaceAllowed: fileExistsInStore
});
}
}
}
@@ -503,7 +514,7 @@ export const AddDownloadsModal = () => {
itemsToAdd[idx] = { ...item, file: newName };
} else if (res.resolution === 'replace') {
if (conflict?.reason.type !== 'file') {
if (conflict?.reason.type !== 'file' || !conflict.replaceAllowed) {
itemsToAdd[idx] = null;
continue;
}
@@ -516,8 +527,6 @@ export const AddDownloadsModal = () => {
const itemLocation = useSharedDestination
? finalLocation
: destinationOverrides[idx] || await categoryLocationForFile(finalFile);
const fullPath = await resolveDownloadFilePath(itemLocation, finalFile);
const store = useDownloadStore.getState();
let existingItem;
const currentSettings = useSettingsStore.getState();
@@ -525,8 +534,8 @@ export const AddDownloadsModal = () => {
const destination = download.destination ||
await resolveCategoryDestination(currentSettings, download.category);
if (
(download.url === item.downloadUrl ||
(destination === itemLocation && download.fileName === finalFile)) &&
destination === itemLocation &&
download.fileName === finalFile &&
download.status !== 'failed'
) {
existingItem = download;
@@ -538,10 +547,10 @@ export const AddDownloadsModal = () => {
throw new Error(`Pause ${existingItem.fileName} before replacing it.`);
}
await invoke('delete_file', { path: fullPath });
if (existingItem) {
await store.removeDownload(existingItem.id);
if (!existingItem) {
throw new Error(`Cannot replace ${finalFile}: file is not owned by a Firelink download.`);
}
await store.removeDownload(existingItem.id, true);
}
}
}
@@ -565,7 +574,7 @@ export const AddDownloadsModal = () => {
}
const category = categoryForFileName(finalFile);
await addDownload({
const added = await addDownload({
id,
url: item.downloadUrl,
fileName: finalFile,
@@ -588,6 +597,9 @@ export const AddDownloadsModal = () => {
mediaFormatSelector: formatSelector,
size: item.size || (item.sizeBytes ? formatBytes(item.sizeBytes) : undefined)
}, action);
if (!added) {
throw new Error('Backend rejected download start.');
}
addedCount += 1;
} catch (e) {
console.error("Invalid URL or failed to add:", e);