mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 22:26:16 -08:00
fix(downloads): clean reservations when ownership writes fail
This commit is contained in:
1 parent
1dfafb404d
commit
4f55bc5e66
3 files changed
+59
-3
No files matched your search
@@ -180,3 +180,48 @@ it('binds a fresh reservation before a replacement can arrive during the HTTP wa
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it.each([false, true])(
|
||||
'cleans a failed reservation journal without deleting a replacement (replaced=%s)',
|
||||
async (replaced) => {
|
||||
const directory = await mkdtemp(
|
||||
join(tmpdir(), 'archive-reservation-failure-')
|
||||
);
|
||||
const filePath = join(directory, 'show.ts');
|
||||
const task: DownloadTask = {
|
||||
id: 20,
|
||||
directory,
|
||||
fileName: 'show.ts',
|
||||
url: 'https://provider.test/archive.ts',
|
||||
catchup: {
|
||||
channelName: 'News',
|
||||
startTimestamp: 100,
|
||||
stopTimestamp: 200,
|
||||
},
|
||||
};
|
||||
const failure = new Error('SQLite write failed');
|
||||
jest.mocked(recordArchivePartial).mockImplementationOnce(async () => {
|
||||
if (replaced) {
|
||||
await rename(filePath + '.part', join(directory, 'original'));
|
||||
await writeFile(filePath + '.part', 'foreign bytes');
|
||||
}
|
||||
throw failure;
|
||||
});
|
||||
try {
|
||||
await expect(
|
||||
reserveTarget({} as DownloadsDatabase, task)
|
||||
).rejects.toBe(failure);
|
||||
if (replaced) {
|
||||
expect(await readFile(filePath + '.part', 'utf8')).toBe(
|
||||
'foreign bytes'
|
||||
);
|
||||
} else {
|
||||
await expect(lstat(filePath + '.part')).rejects.toMatchObject({
|
||||
code: 'ENOENT',
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1,5 +1,6 @@
|
||||
import { closeSync, fstatSync, openSync } from 'node:fs';
|
||||
import { lstat } from 'node:fs/promises';
|
||||
import { cleanupCatchupPartial } from './download-catchup-cleanup';
|
||||
import { cleanupStoredCatchupPartial } from './download-catchup-removal';
|
||||
import {
|
||||
clearArchiveFinalization,
|
||||
@@ -67,6 +68,13 @@ export async function reserveOwnedCatchupTarget(
|
||||
const identity = task.catchupExpectedPartialIdentity;
|
||||
if (!identity)
|
||||
throw new Error('Archive reservation identity is unavailable');
|
||||
await recordArchivePartial(db, task.id, reservation.path, identity);
|
||||
try {
|
||||
await recordArchivePartial(db, task.id, reservation.path, identity);
|
||||
} catch (error) {
|
||||
// No media bytes have been written. Without a usable journal, remove
|
||||
// only the empty reservation whose identity came from our descriptor.
|
||||
await cleanupCatchupPartial(reservation.path, identity);
|
||||
throw error;
|
||||
}
|
||||
return reservation;
|
||||
}
|
||||
Reference in new issue
Block a user