Media cache on the homelab USB drive with an offline guard; Topic channels list their tracks
- MEDIA_DIR moves to /mnt/data/ytplayer (bind-mounted at /app/bulk); DB, lyrics, notes and uploads stay on the ytplayer-data volume - MEDIA_VOLUME_MARKER: while the marker on the drive is missing the cache writes, serves and deletes nothing, so playback streams and an unmounted drive never fills the disk under its mountpoint or loses its index on a reboot - Budget 250 GiB, keep 20 GiB free on the drive - /api/channel falls back to the uploads playlist and the channel home page when a channel has no Videos tab (auto-generated '- Topic' music channels)
This commit is contained in:
@@ -389,3 +389,43 @@ describe('media cache jobs', () => {
|
||||
expect(await cache.filePath('..', 1)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('removable cache volume (volumeMarker)', () => {
|
||||
test('offline volume: nothing is written, nothing served, rows survive a reboot', async () => {
|
||||
await clearDb();
|
||||
const markerDir = mkdtempSync(join(tmpdir(), 'ytp-vol-'));
|
||||
const marker = join(markerDir, '.hl-data');
|
||||
writeFileSync(marker, '');
|
||||
const seed = makeCache({ volumeMarker: marker });
|
||||
await seed.cache.init();
|
||||
const row = await seed.cache.ensureCached('volAAAAAAA1', { priority: HIGH });
|
||||
expect(row.status).toBe('ready');
|
||||
expect(await seed.cache.filePath('volAAAAAAA1')).not.toBeNull();
|
||||
|
||||
// Drive unplugged: the marker vanishes.
|
||||
rmSync(marker);
|
||||
expect(seed.cache.available()).toBe(false);
|
||||
expect(await seed.cache.filePath('volAAAAAAA1')).toBeNull();
|
||||
expect(await seed.cache.getReady('volAAAAAAA1')).toBeNull();
|
||||
await expect(seed.cache.ensureCached('volAAAAAAA2', { priority: HIGH })).rejects.toBeInstanceOf(MediaSkip);
|
||||
await expect(seed.cache.ensureCached('volAAAAAAA1', { priority: HIGH })).rejects.toBeInstanceOf(MediaSkip);
|
||||
expect(seed.calls).toEqual(['volAAAAAAA1']);
|
||||
|
||||
// Reboot while offline must not reconcile (it would drop every row).
|
||||
const again = makeCache({ volumeMarker: marker, dir: seed.dir });
|
||||
await again.cache.init();
|
||||
expect((await dbmod.getMedia('volAAAAAAA1')).status).toBe('ready');
|
||||
expect(existsSync(join(seed.dir, `volAAAAAAA1.${row.gen}.mp4`))).toBe(true);
|
||||
|
||||
// Drive back: the same copy is served again without a refetch.
|
||||
writeFileSync(marker, '');
|
||||
expect(await again.cache.filePath('volAAAAAAA1')).not.toBeNull();
|
||||
rmSync(markerDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('no marker configured behaves exactly as before', async () => {
|
||||
await clearDb();
|
||||
const { cache } = makeCache();
|
||||
expect(cache.available()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user