fix: m3u sync stale purge bug

This commit is contained in:
Trevor Mears
2026-01-14 17:32:19 -08:00
parent 0d16182cdf
commit aa0fb7b956
+55 -29
View File
@@ -252,11 +252,17 @@ class SyncService {
/** /**
* Batch save streams (channels, vod, series) * Batch save streams (channels, vod, series)
* Also purges stale entries that no longer exist in the source * Also purges stale entries that no longer exist in the source (unless skipPurge is true)
* @param {number} sourceId - Source ID
* @param {string} type - Type of items (live, movie, series)
* @param {Array} items - Items to save
* @param {Object} options - Options { skipPurge: boolean }
* @returns {Set} Set of synced IDs (for external purge if skipPurge was true)
*/ */
async saveStreams(sourceId, type, items) { async saveStreams(sourceId, type, items, options = {}) {
if (!items || items.length === 0) return; if (!items || items.length === 0) return new Set();
const db = getDb(); const db = getDb();
const { skipPurge = false } = options;
// Collect all IDs we're syncing // Collect all IDs we're syncing
const syncedIds = new Set(); const syncedIds = new Set();
@@ -335,33 +341,46 @@ class SyncService {
await new Promise(resolve => setImmediate(resolve)); await new Promise(resolve => setImmediate(resolve));
} }
// Purge stale entries that no longer exist in the source // Purge stale entries (skip if doing batch sync like M3U)
// Use temp table approach to avoid SQL parameter limits with large playlists if (!skipPurge && syncedIds.size > 0) {
if (syncedIds.size > 0) { await this.purgeStaleItems(sourceId, type, syncedIds);
db.exec('CREATE TEMP TABLE IF NOT EXISTS synced_ids (id TEXT PRIMARY KEY)');
db.exec('DELETE FROM synced_ids');
const insertTemp = db.prepare('INSERT OR IGNORE INTO synced_ids (id) VALUES (?)');
const insertTempBatch = db.transaction((ids) => {
for (const id of ids) {
insertTemp.run(id);
}
});
insertTempBatch([...syncedIds]);
const deleteStmt = db.prepare(`
DELETE FROM playlist_items
WHERE source_id = ? AND type = ?
AND id NOT IN (SELECT id FROM synced_ids)
`);
const deleted = deleteStmt.run(sourceId, type);
if (deleted.changes > 0) {
console.log(`[Sync] Purged ${deleted.changes} stale ${type} items`);
}
} }
console.log(`[Sync] Saved ${items.length} ${type} items`); console.log(`[Sync] Saved ${items.length} ${type} items`);
return syncedIds;
}
/**
* Purge stale items that are no longer in the source
* @param {number} sourceId - Source ID
* @param {string} type - Type of items (live, movie, series)
* @param {Set} syncedIds - Set of IDs that should be kept
*/
async purgeStaleItems(sourceId, type, syncedIds) {
if (!syncedIds || syncedIds.size === 0) return;
const db = getDb();
db.exec('CREATE TEMP TABLE IF NOT EXISTS synced_ids (id TEXT PRIMARY KEY)');
db.exec('DELETE FROM synced_ids');
const insertTemp = db.prepare('INSERT OR IGNORE INTO synced_ids (id) VALUES (?)');
const insertTempBatch = db.transaction((ids) => {
for (const id of ids) {
insertTemp.run(id);
}
});
insertTempBatch([...syncedIds]);
const deleteStmt = db.prepare(`
DELETE FROM playlist_items
WHERE source_id = ? AND type = ?
AND id NOT IN (SELECT id FROM synced_ids)
`);
const deleted = deleteStmt.run(sourceId, type);
if (deleted.changes > 0) {
console.log(`[Sync] Purged ${deleted.changes} stale ${type} items`);
}
} }
@@ -489,6 +508,7 @@ class SyncService {
logMemory(); logMemory();
const allGroups = new Set(); const allGroups = new Set();
const allSyncedIds = new Set(); // Collect IDs across all batches
let totalChannels = 0; let totalChannels = 0;
let batchCount = 0; let batchCount = 0;
@@ -506,9 +526,10 @@ class SyncService {
tvgId: ch.tvgId || null, tvgId: ch.tvgId || null,
})); }));
// Save this batch immediately // Save this batch immediately (skip purge - we'll do it at the end)
if (playlistItems.length > 0) { if (playlistItems.length > 0) {
await this.saveStreams(source.id, 'live', playlistItems); const batchIds = await this.saveStreams(source.id, 'live', playlistItems, { skipPurge: true });
batchIds.forEach(id => allSyncedIds.add(id));
totalChannels += playlistItems.length; totalChannels += playlistItems.length;
} }
@@ -525,6 +546,11 @@ class SyncService {
console.log(`[Sync] M3U Parsed: ${totalChannels} channels, ${allGroups.size} groups`); console.log(`[Sync] M3U Parsed: ${totalChannels} channels, ${allGroups.size} groups`);
logMemory(); logMemory();
// Purge stale items after all batches are complete
if (allSyncedIds.size > 0) {
await this.purgeStaleItems(source.id, 'live', allSyncedIds);
}
// Save Categories (Groups) at the end // Save Categories (Groups) at the end
const categories = Array.from(allGroups).map(name => ({ const categories = Array.from(allGroups).map(name => ({
category_id: name, category_id: name,