mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 11:06:16 -08:00
fix(downloads): probe restored files asynchronously
This commit is contained in:
1 parent
33ca53e86d
commit
d14cf5fbda
4 files changed
+24
-11
No files matched your search
@@ -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',
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user