mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
fix(epg): remove cached XMLTV data after source deletion (#1548)
* fix(epg): remove cached XMLTV data after source deletion * fix(epg): close source reconciliation review races * fix(epg): serialize cleanup with replacement imports * fix(epg): report retired worker exits as cancellations * fix(epg): preserve source metadata through cache cleanup * refactor(epg): separate worker runtime and import lifecycle * fix(epg): skip cleanup for unchanged source settings * fix(epg): cancel retired error rows and pending retries * fix(epg): redact diagnostics and mirror committed settings after cleanup errors * fix(epg): preserve metadata writer order independently of timestamps
This commit is contained in:
1 parent
baba0529ef
commit
9de480826c
55 files changed
+3538
-615
No files matched your search
@@ -287,6 +287,18 @@ const CREATE_TABLE_STATEMENTS = [
|
||||
source_url TEXT NOT NULL,
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
)`,
|
||||
`CREATE TABLE IF NOT EXISTS epg_channel_sources (
|
||||
channel_id TEXT NOT NULL,
|
||||
source_url TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL,
|
||||
icon_url TEXT,
|
||||
url TEXT,
|
||||
updated_at TEXT DEFAULT (datetime('now')),
|
||||
write_order INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (channel_id, source_url),
|
||||
FOREIGN KEY (channel_id) REFERENCES epg_channels(id) ON DELETE CASCADE
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_epg_channel_sources_source ON epg_channel_sources(source_url)`,
|
||||
`CREATE TABLE IF NOT EXISTS epg_programs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
channel_id TEXT NOT NULL,
|
||||
@@ -419,6 +431,8 @@ const COLUMN_MIGRATION_STATEMENTS = [
|
||||
`ALTER TABLE content ADD COLUMN original_title TEXT`,
|
||||
// v1.7.1: Scope XMLTV programs to their source URL for playlist-local EPG lookup
|
||||
`ALTER TABLE epg_programs ADD COLUMN source_url TEXT`,
|
||||
// Preserve writer order independently of wall-clock precision or changes.
|
||||
`ALTER TABLE epg_channel_sources ADD COLUMN write_order INTEGER NOT NULL DEFAULT 0`,
|
||||
// Pause/resume: entity validator (ETag/Last-Modified) sent as If-Range on resume
|
||||
`ALTER TABLE downloads ADD COLUMN resume_validator TEXT`,
|
||||
// Offline details: provider-neutral display metadata captured at download time
|
||||
|
||||
@@ -11,6 +11,7 @@ import { sql } from 'drizzle-orm';
|
||||
import {
|
||||
index,
|
||||
integer,
|
||||
primaryKey,
|
||||
sqliteTable,
|
||||
text,
|
||||
uniqueIndex,
|
||||
@@ -204,6 +205,26 @@ export const epgChannels = sqliteTable(
|
||||
})
|
||||
);
|
||||
|
||||
// A global XMLTV ID can be shared by sources with different channel metadata.
|
||||
export const epgChannelSources = sqliteTable(
|
||||
'epg_channel_sources',
|
||||
{
|
||||
channelId: text('channel_id')
|
||||
.notNull()
|
||||
.references(() => epgChannels.id, { onDelete: 'cascade' }),
|
||||
sourceUrl: text('source_url').notNull(),
|
||||
displayName: text('display_name').notNull(),
|
||||
iconUrl: text('icon_url'),
|
||||
url: text('url'),
|
||||
updatedAt: text('updated_at').default(sql`CURRENT_TIMESTAMP`),
|
||||
writeOrder: integer('write_order').notNull().default(0),
|
||||
},
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.channelId, table.sourceUrl] }),
|
||||
index('idx_epg_channel_sources_source').on(table.sourceUrl),
|
||||
]
|
||||
);
|
||||
|
||||
// EPG Programs table
|
||||
export const epgPrograms = sqliteTable(
|
||||
'epg_programs',
|
||||
|
||||
@@ -85,6 +85,7 @@ export type ElectronBridgePlaylistType =
|
||||
(typeof ELECTRON_BRIDGE_PLAYLIST_TYPES)[keyof typeof ELECTRON_BRIDGE_PLAYLIST_TYPES];
|
||||
|
||||
export const ELECTRON_BRIDGE_EPG_PROGRESS_STATUSES = {
|
||||
Cancelled: 'cancelled',
|
||||
Complete: 'complete',
|
||||
Error: 'error',
|
||||
Loading: 'loading',
|
||||
@@ -340,8 +341,7 @@ export interface ElectronBridgeTrustOptions {
|
||||
trustedInsecureTlsHosts?: string[];
|
||||
}
|
||||
|
||||
export interface ElectronBridgePlaylistFetchOptions
|
||||
extends ElectronBridgeTrustOptions {
|
||||
export interface ElectronBridgePlaylistFetchOptions extends ElectronBridgeTrustOptions {
|
||||
userAgent?: string;
|
||||
}
|
||||
|
||||
@@ -375,8 +375,7 @@ export interface ElectronBridgeEpgLookupOptions {
|
||||
* renderer will also present as "now". Absent, the main process uses its own
|
||||
* clock.
|
||||
*/
|
||||
export interface ElectronBridgeCurrentProgramsOptions
|
||||
extends ElectronBridgeEpgLookupOptions {
|
||||
export interface ElectronBridgeCurrentProgramsOptions extends ElectronBridgeEpgLookupOptions {
|
||||
nowMs?: number;
|
||||
}
|
||||
|
||||
@@ -387,6 +386,7 @@ export interface ElectronBridgeEpgProgressStats {
|
||||
|
||||
export interface ElectronBridgeEpgProgress {
|
||||
url: string;
|
||||
generation?: number;
|
||||
status: ElectronBridgeEpgProgressStatus;
|
||||
stats?: ElectronBridgeEpgProgressStats;
|
||||
error?: string;
|
||||
@@ -812,6 +812,7 @@ export interface ElectronBridgeApi {
|
||||
options?: ElectronBridgeTrustOptions
|
||||
) => Promise<ElectronBridgeEpgFetchResult>;
|
||||
clearEpgData: () => Promise<ElectronBridgeResult>;
|
||||
reconcileEpgSources: (urls: string[]) => Promise<ElectronBridgeResult>;
|
||||
clearEpgDataForSource: (sourceUrl: string) => Promise<ElectronBridgeResult>;
|
||||
checkEpgFreshness: (
|
||||
urls: string[],
|
||||
@@ -1291,7 +1292,9 @@ export interface ElectronBridgeApi {
|
||||
recordingsGet?: (
|
||||
recordingId: number
|
||||
) => Promise<ElectronRecordingItem | null>;
|
||||
recordingsStop?: (recordingId: number) => Promise<ElectronBridgeErrorResult>;
|
||||
recordingsStop?: (
|
||||
recordingId: number
|
||||
) => Promise<ElectronBridgeErrorResult>;
|
||||
recordingsRemove?: (
|
||||
recordingId: number
|
||||
) => Promise<ElectronBridgeErrorResult>;
|
||||
|
||||
Reference in new issue
Block a user