fix(downloads): probe restored files asynchronously

This commit is contained in:
4gray committed 2026-08-02 20:30:33 +02:00
1 parent 33ca53e86d
commit d14cf5fbda
4 files changed
+24 -11

No files matched your search

+6 -5
View File
@@ -944,11 +944,12 @@ engine` (restart required) or
transfer with a FIFO queue. `DOWNLOADS_START` remains the sole start IPC; its
stable `reason: 'already-in-progress'` and `reason: 'already-downloaded'`
results are counted as skipped, and no batch IPC is introduced. The latter
comes from a main-process filesystem recheck before a completed-missing row
can be reset, so a file restored after the renderer snapshot is not orphaned
or downloaded again. Episode and season download actions require an
authoritative global list. A successful snapshot remains authoritative while
a later background refresh is in flight; a latest refresh failure leaves
comes from an asynchronous main-process filesystem recheck before a
completed-missing row can be reset, so a file restored after the renderer
snapshot is not orphaned or downloaded again. Episode and season download
actions require an authoritative global list. A successful snapshot remains
authoritative while a later background refresh is in flight; a latest
refresh failure leaves
loading/empty-state resolution intact but disables starts until another
snapshot succeeds.
- Episode ownership uses normalized `episode.id` as the canonical `xtreamId`
@@ -41,6 +41,10 @@ async function setupStartMetadataRequest(
update: jest.fn(() => ({ set })),
};
const enqueueDownload = jest.fn();
const getDownloadFileAvailabilityAsync = jest.fn(async () =>
completedFileAvailable ? 'available' : 'missing'
);
const isAvailableDownloadFile = jest.fn(() => completedFileAvailable);
const authorizer = {
requireAuthorized: jest.fn(async (directory: string) => directory),
} as unknown as DownloadDirectoryAuthorizer;
@@ -55,7 +59,8 @@ async function setupStartMetadataRequest(
enqueueDownload,
}));
jest.doMock('./download-file-availability', () => ({
isAvailableDownloadFile: jest.fn(() => completedFileAvailable),
getDownloadFileAvailabilityAsync,
isAvailableDownloadFile,
}));
const { startDownloadRequest } = await import('./download-requests');
@@ -64,7 +69,9 @@ async function setupStartMetadataRequest(
db,
downloadLimit,
enqueueDownload,
getDownloadFileAvailabilityAsync,
insertValues,
isAvailableDownloadFile,
set,
startDownloadRequest,
};
@@ -461,6 +468,10 @@ describe('download request identity resolution', () => {
expect(request.db.insert).not.toHaveBeenCalled();
expect(request.db.update).not.toHaveBeenCalled();
expect(request.enqueueDownload).not.toHaveBeenCalled();
expect(request.getDownloadFileAvailabilityAsync).toHaveBeenCalledWith(
completedRow
);
expect(request.isAvailableDownloadFile).not.toHaveBeenCalled();
});
it.each(['queued', 'downloading', 'paused'] as const)(
@@ -11,7 +11,7 @@ import * as schema from '../../database/schema';
import { assertRemoteUrlAllowed } from '../url-safety';
import { DownloadDirectoryAuthorizer } from './download-directory-authorization';
import { removePartialDownloadFile } from './download-file-path';
import { isAvailableDownloadFile } from './download-file-availability';
import { getDownloadFileAvailabilityAsync } from './download-file-availability';
import { resolveExistingDownloadIdentity } from './download-request-identity';
import { resolveStoredDownloadHeaders } from './download-request-headers';
import {
@@ -150,7 +150,7 @@ export async function startDownloadRequest(
if (
item.contentType === 'episode' &&
item.status === 'completed' &&
isAvailableDownloadFile(item.filePath)
(await getDownloadFileAvailabilityAsync(item)) === 'available'
) {
return {
error: 'Download already completed',
+4 -3
View File
@@ -93,9 +93,10 @@ variants, contextual buttons, and theme-aware styling.
whose file is available or whose availability is still unknown. Failed,
canceled, completed-missing, and unambiguous row-less episodes are eligible;
a completed-missing row is restarted as a fresh download. Before resetting
such a completed row, `DOWNLOADS_START` rechecks its retained path in the main
process. A restored file returns stable `reason: 'already-downloaded'` without
mutation; active matches return `reason: 'already-in-progress'`. The
such a completed row, `DOWNLOADS_START` asynchronously rechecks its retained
path in the main process. A restored file returns stable
`reason: 'already-downloaded'` without mutation; active matches return
`reason: 'already-in-progress'`. The
coordinator counts both as skipped. There is no batch IPC, parallel transfer,
or queue reordering: destination authorization, persisted header handling,
and the backend's one-active-transfer FIFO semantics remain unchanged.