mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-09 01:16:15 -08:00
fix(downloads): reset archive proof before an explicit restart
This commit is contained in:
1 parent
8dfd06c05f
commit
77a70af8e9
6 files changed
+51
-16
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { inArray } from 'drizzle-orm';
|
||||
import { eq, inArray } from 'drizzle-orm';
|
||||
import { lstatSync } from 'node:fs';
|
||||
import { isAbsolute } from 'node:path';
|
||||
import * as schema from '../../database/schema';
|
||||
@@ -38,6 +38,16 @@ export async function recordArchiveFinalization(
|
||||
});
|
||||
}
|
||||
|
||||
/** An explicitly restarted transfer must never inherit an earlier attempt's proof. */
|
||||
export async function clearArchiveFinalization(
|
||||
db: DownloadsDatabase,
|
||||
downloadId: number
|
||||
): Promise<void> {
|
||||
await db
|
||||
.delete(schema.downloadArchiveFinalizations)
|
||||
.where(eq(schema.downloadArchiveFinalizations.downloadId, downloadId));
|
||||
}
|
||||
|
||||
function identity(value: unknown): value is ArchiveFileIdentity {
|
||||
if (!value || typeof value !== 'object') return false;
|
||||
const candidate = value as ArchiveFileIdentity;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { clearArchiveFinalization } from './download-catchup-journal';
|
||||
import {
|
||||
mkdtemp,
|
||||
readFile,
|
||||
@@ -26,6 +27,10 @@ import {
|
||||
} from './download-catchup-limits';
|
||||
import { stat } from 'node:fs/promises';
|
||||
|
||||
jest.mock('./download-catchup-journal', () => ({
|
||||
...jest.requireActual('./download-catchup-journal'),
|
||||
clearArchiveFinalization: jest.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
jest.mock('./download-catchup-limits', () => {
|
||||
const actual = jest.requireActual('./download-catchup-limits');
|
||||
return {
|
||||
@@ -118,6 +123,14 @@ describe('TS archive transfer', () => {
|
||||
};
|
||||
try {
|
||||
await writeFile(path + '.part', 'old data');
|
||||
jest.mocked(clearArchiveFinalization).mockImplementationOnce(
|
||||
async (_db, id) => {
|
||||
expect(id).toBe(task.id);
|
||||
expect(await readFile(path + '.part', 'utf8')).toBe(
|
||||
'old data'
|
||||
);
|
||||
}
|
||||
);
|
||||
const actualLimit = jest.requireActual<
|
||||
typeof import('./download-catchup-limits')
|
||||
>('./download-catchup-limits').getArchiveByteLimit;
|
||||
@@ -167,6 +180,10 @@ describe('TS archive transfer', () => {
|
||||
expect.anything()
|
||||
);
|
||||
expect(task.resumeValidator).toBeNull();
|
||||
expect(clearArchiveFinalization).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
task.id
|
||||
);
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { clearArchiveFinalization } from './download-catchup-journal';
|
||||
import {
|
||||
assertArchiveCopyHeadroom,
|
||||
createArchiveByteGuard,
|
||||
@@ -70,6 +71,7 @@ export async function transferCatchupToPartialFile(
|
||||
let output: Awaited<ReturnType<typeof openCatchupOutput>> | undefined;
|
||||
let pendingProgress = Promise.resolve();
|
||||
try {
|
||||
await clearArchiveFinalization(db, task.id);
|
||||
const response = await requestWithValidatedRedirects<Readable>(
|
||||
task.url,
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { removePartialDownloadFile } from './download-file-path';
|
||||
import { constants } from 'node:fs';
|
||||
import { copyFile, link, stat, unlink } from 'node:fs/promises';
|
||||
import type { ReservedPartialDownloadFile } from './download-file-path';
|
||||
@@ -61,3 +62,16 @@ function canCopyCompletedPartialAfterLinkFailure(error: unknown): boolean {
|
||||
errorCode === 'EXDEV'
|
||||
);
|
||||
}
|
||||
|
||||
/** @returns false when a .part exists but could not be deleted. */
|
||||
export function removePartialFile(
|
||||
filePath: string | null | undefined
|
||||
): boolean {
|
||||
try {
|
||||
removePartialDownloadFile(filePath);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[Downloads] Failed to delete partial file:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import { recordArchiveFinalization } from './download-catchup-journal';
|
||||
import { finalizePartialDownload } from './download-file-finalize';
|
||||
import {
|
||||
finalizePartialDownload,
|
||||
removePartialFile,
|
||||
} from './download-file-finalize';
|
||||
import { cleanupCatchupPartial } from './download-catchup-cleanup';
|
||||
import {
|
||||
finalizeCatchupPartial,
|
||||
@@ -12,7 +15,6 @@ import * as schema from '../../database/schema';
|
||||
import {
|
||||
getPartialDownloadPath,
|
||||
getPartialDownloadSize,
|
||||
removePartialDownloadFile,
|
||||
type ReservedPartialDownloadFile,
|
||||
} from './download-file-path';
|
||||
import type {
|
||||
@@ -343,15 +345,4 @@ export function getPausedByteCount(task: DownloadTask): number {
|
||||
}
|
||||
}
|
||||
|
||||
/** @returns false when a .part exists but could not be deleted. */
|
||||
export function removePartialFile(
|
||||
filePath: string | null | undefined
|
||||
): boolean {
|
||||
try {
|
||||
removePartialDownloadFile(filePath);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[Downloads] Failed to delete partial file:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export { removePartialFile } from './download-file-finalize';
|
||||
@@ -64,7 +64,8 @@ requires that proof and a matching regular file, identity and size to recover an
|
||||
archive; termination before promotion leaves the verified partial paused. An
|
||||
owned incomplete copy is removed by journal identity before the source resumes. The
|
||||
journal also makes startup partial cleanup identity-aware and remains with
|
||||
completed archives until they are removed. Process-local proof allows immediate
|
||||
completed archives until they are removed. An explicitly restarted transfer
|
||||
clears the previous attempt's proof before network requests or partial-file writes. Process-local proof allows immediate
|
||||
recovery after a transient completion DB error without waiting for a restart.
|
||||
An explicit cancellation of a queued/paused archive captures the selected regular
|
||||
partial using the same cleanup helper; symlink entries are preserved.
|
||||
|
||||
Reference in new issue
Block a user