mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 02:46:16 -08:00
feat(downloads): queue season episode downloads (#1357)
* docs(downloads): specify season queueing * docs(downloads): plan season queue implementation * feat(downloads): define episode queue identity * fix(downloads): align episode identity contract * feat(downloads): coordinate season queue submissions * fix(downloads): keep queue coordination provider neutral * fix(downloads): reconcile legacy episode identities * fix(downloads): fail closed on invalid stored coordinates * refactor(downloads): adapt Xtream episode requests * fix(downloads): use canonical Stalker episode ids * test(downloads): cover Stalker adapter reactivity * feat(downloads): add selected season queue action * refactor(downloads): extract season download presenter * feat(downloads): localize season queue feedback * test(downloads): cover series batch queue flow * test(downloads): harden series queue fixtures * docs(downloads): describe season queueing * docs(downloads): clarify season queue IPC contract * fix(downloads): isolate season header build warnings * fix(downloads): label season view toggles * fix(downloads): preserve Xtream episode headers * fix(downloads): fail closed on stale episode state * fix(downloads): align renderer queue safeguards * fix(downloads): block ambiguous episode actions * fix(downloads): accept nullable legacy coordinates * fix(downloads): preserve scoped episode ownership * fix(downloads): probe restored files asynchronously * fix(downloads): bound restored file probes * fix(downloads): release timed out file probes * fix(downloads): bound file probe callers * fix(downloads): refresh stable season skips * fix(downloads): fail closed before provider prep * fix(downloads): preserve retained partial ownership * fix(downloads): reconcile partial cleanup completion * fix(downloads): await authoritative list refresh * fix(downloads): coalesce list refreshes * fix(downloads): preserve specials season identity * fix(stalker): preserve specials season mapping * fix(downloads): distinguish missing Xtream seasons
This commit is contained in:
1 parent
d3cc18dc72
commit
96facd6f49
79 files changed
+8352
-1255
No files matched your search
@@ -25,6 +25,7 @@ function createdObjectNames(prefix: string, statements: readonly string[]) {
|
||||
}
|
||||
|
||||
function rebuildDownloadsInElectron(metadataSnapshot: string): {
|
||||
episodeIdentityScope: string;
|
||||
metadataSnapshot: string;
|
||||
retainedAfterPlaylistDelete: boolean;
|
||||
schemaSql: string;
|
||||
@@ -51,6 +52,7 @@ function rebuildDownloadsInElectron(metadataSnapshot: string): {
|
||||
series_xtream_id INTEGER,
|
||||
season_number INTEGER,
|
||||
episode_number INTEGER,
|
||||
episode_identity_scope TEXT,
|
||||
title TEXT NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
file_name TEXT,
|
||||
@@ -84,20 +86,22 @@ function rebuildDownloadsInElectron(metadataSnapshot: string): {
|
||||
content_type,
|
||||
title,
|
||||
url,
|
||||
metadata_snapshot
|
||||
) VALUES (?, ?, ?, ?, ?, ?)
|
||||
metadata_snapshot,
|
||||
episode_identity_scope
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
\`).run(
|
||||
'playlist-1',
|
||||
42,
|
||||
'vod',
|
||||
'Offline title',
|
||||
'https://example.com/movie',
|
||||
metadataSnapshot
|
||||
metadataSnapshot,
|
||||
'stalker-lazy-vod'
|
||||
);
|
||||
console.log = () => undefined;
|
||||
__databaseConnectionTestHooks.ensureDownloadsPauseResumeSchema(sqlite);
|
||||
const row = sqlite.prepare(
|
||||
'SELECT metadata_snapshot FROM downloads WHERE id = 1'
|
||||
'SELECT metadata_snapshot, episode_identity_scope FROM downloads WHERE id = 1'
|
||||
).get();
|
||||
const table = sqlite.prepare(
|
||||
"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'downloads'"
|
||||
@@ -109,6 +113,7 @@ function rebuildDownloadsInElectron(metadataSnapshot: string): {
|
||||
).get());
|
||||
sqlite.close();
|
||||
process.stdout.write(JSON.stringify({
|
||||
episodeIdentityScope: row.episode_identity_scope,
|
||||
metadataSnapshot: row.metadata_snapshot,
|
||||
retainedAfterPlaylistDelete,
|
||||
schemaSql: table.sql,
|
||||
@@ -129,6 +134,7 @@ function rebuildDownloadsInElectron(metadataSnapshot: string): {
|
||||
);
|
||||
|
||||
return JSON.parse(output) as {
|
||||
episodeIdentityScope: string;
|
||||
metadataSnapshot: string;
|
||||
retainedAfterPlaylistDelete: boolean;
|
||||
schemaSql: string;
|
||||
@@ -240,6 +246,8 @@ describe('database schema statements', () => {
|
||||
expect(schemaSql).toContain('resume_validator TEXT');
|
||||
expect(schemaSql).toContain('metadata_snapshot TEXT');
|
||||
expect(downloadColumns).toContain('metadata_snapshot');
|
||||
expect(schemaSql).toContain('episode_identity_scope TEXT');
|
||||
expect(downloadColumns).toContain('episode_identity_scope');
|
||||
expect(schemaSql).toContain("'paused'");
|
||||
expect(schemaSql).toContain(
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS favorites_content_playlist_unique'
|
||||
@@ -288,6 +296,9 @@ describe('database schema statements', () => {
|
||||
expect(columnMigrationStatements).toContain(
|
||||
'ALTER TABLE downloads ADD COLUMN metadata_snapshot TEXT'
|
||||
);
|
||||
expect(columnMigrationStatements).toContain(
|
||||
'ALTER TABLE downloads ADD COLUMN episode_identity_scope TEXT'
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps legacy index migrations idempotent IF NOT EXISTS statements', () => {
|
||||
@@ -658,6 +669,7 @@ describe('database schema statements', () => {
|
||||
expect(rebuilt.schemaSql).toContain(`'paused'`);
|
||||
expect(rebuilt.schemaSql).not.toContain('REFERENCES playlists');
|
||||
expect(rebuilt.metadataSnapshot).toBe(metadataSnapshot);
|
||||
expect(rebuilt.episodeIdentityScope).toBe('stalker-lazy-vod');
|
||||
expect(rebuilt.retainedAfterPlaylistDelete).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ const DOWNLOADS_TABLE_SQL = `CREATE TABLE IF NOT EXISTS downloads (
|
||||
series_xtream_id INTEGER,
|
||||
season_number INTEGER,
|
||||
episode_number INTEGER,
|
||||
episode_identity_scope TEXT,
|
||||
title TEXT NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
file_name TEXT,
|
||||
@@ -381,6 +382,8 @@ const COLUMN_MIGRATION_STATEMENTS = [
|
||||
`ALTER TABLE downloads ADD COLUMN resume_validator TEXT`,
|
||||
// Offline details: provider-neutral display metadata captured at download time
|
||||
`ALTER TABLE downloads ADD COLUMN metadata_snapshot TEXT`,
|
||||
// Series queue: scope coordinate compatibility across provider series modes
|
||||
`ALTER TABLE downloads ADD COLUMN episode_identity_scope TEXT`,
|
||||
];
|
||||
|
||||
const INDEX_MIGRATION_STATEMENTS = [
|
||||
@@ -1010,6 +1013,12 @@ function ensureDownloadsPauseResumeSchema(sqliteDb: Database.Database): void {
|
||||
const legacyResumeValidatorSelect = hasResumeValidator
|
||||
? 'resume_validator'
|
||||
: 'NULL AS resume_validator';
|
||||
const hasEpisodeIdentityScope = row.sql.includes(
|
||||
'episode_identity_scope'
|
||||
);
|
||||
const legacyEpisodeIdentityScopeSelect = hasEpisodeIdentityScope
|
||||
? 'episode_identity_scope'
|
||||
: 'NULL AS episode_identity_scope';
|
||||
const rebuild = sqliteDb.transaction(() => {
|
||||
for (const statement of DOWNLOADS_INDEX_STATEMENTS) {
|
||||
const match = statement.match(
|
||||
@@ -1036,6 +1045,7 @@ function ensureDownloadsPauseResumeSchema(sqliteDb: Database.Database): void {
|
||||
series_xtream_id,
|
||||
season_number,
|
||||
episode_number,
|
||||
episode_identity_scope,
|
||||
title,
|
||||
url,
|
||||
file_name,
|
||||
@@ -1059,6 +1069,7 @@ function ensureDownloadsPauseResumeSchema(sqliteDb: Database.Database): void {
|
||||
series_xtream_id,
|
||||
season_number,
|
||||
episode_number,
|
||||
${legacyEpisodeIdentityScopeSelect},
|
||||
title,
|
||||
url,
|
||||
file_name,
|
||||
|
||||
@@ -323,6 +323,7 @@ export const downloads = sqliteTable(
|
||||
seriesXtreamId: integer('series_xtream_id'),
|
||||
seasonNumber: integer('season_number'),
|
||||
episodeNumber: integer('episode_number'),
|
||||
episodeIdentityScope: text('episode_identity_scope'),
|
||||
// Download metadata
|
||||
title: text('title').notNull(),
|
||||
url: text('url').notNull(),
|
||||
|
||||
Reference in new issue
Block a user