fix(live): prevent analysis crash on large catalogs (#26)
Bound synchronous live channel enrichment and tolerate minimal projection payloads so analysis and listings do not blank the UI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -5,6 +5,8 @@ const { matchesSearch } = require('./flexibleChannelSearch');
|
||||
const iptvOrgCatalog = require('./iptvOrgCatalogService');
|
||||
const epgMatching = require('./epgMatchingService');
|
||||
|
||||
const MAX_SYNCHRONOUS_ENRICHMENTS = 250;
|
||||
|
||||
const QUALITY_TOKENS = new Set([
|
||||
'4k', 'uhd', 'fhd', 'fullhd', 'hd', 'sd', 'hevc', 'h265', 'h264',
|
||||
'1080p', '1080i', '720p', '576p', '480p'
|
||||
@ -357,17 +359,24 @@ function getChannelPage({ sourceId = null, includeHidden = false, userId = null,
|
||||
params.push(sourceId);
|
||||
}
|
||||
const rows = database.prepare(`
|
||||
SELECT p.*, c.name AS category_name, COALESCE(c.is_hidden, 0) AS category_hidden,
|
||||
CASE WHEN f.id IS NULL THEN 0 ELSE 1 END AS user_favorite,
|
||||
SELECT p.*, CASE WHEN f.id IS NULL THEN 0 ELSE 1 END AS user_favorite,
|
||||
l.display_name, l.preferred_variant_id
|
||||
FROM playlist_items p
|
||||
JOIN logical_channels l ON l.id = p.logical_channel_id
|
||||
LEFT JOIN categories c ON c.source_id = p.source_id AND c.type = 'live' AND c.category_id = p.category_id
|
||||
LEFT JOIN favorites f ON f.user_id = ? AND f.source_id = p.source_id
|
||||
AND f.item_id = p.item_id AND f.item_type = 'channel'
|
||||
WHERE p.type = 'live' ${sourceFilter}
|
||||
ORDER BY l.display_name, p.variant_rank
|
||||
`).all(userId ?? -1, ...params);
|
||||
const categoryRows = sourceId === null
|
||||
? database.prepare("SELECT source_id, category_id, name, is_hidden FROM categories WHERE type = 'live'").all()
|
||||
: database.prepare("SELECT source_id, category_id, name, is_hidden FROM categories WHERE type = 'live' AND source_id = ?").all(sourceId);
|
||||
const categories = new Map(categoryRows.map(category => [`${category.source_id}\0${category.category_id}`, category]));
|
||||
for (const row of rows) {
|
||||
const category = categories.get(`${row.source_id}\0${row.category_id}`);
|
||||
row.category_name = category?.name || null;
|
||||
row.category_hidden = category?.is_hidden || 0;
|
||||
}
|
||||
const serialized = serializeChannels(rows, includeHidden);
|
||||
const availableLanguages = [...new Map(serialized.map(channel => [channel.language.code, channel.language])).values()]
|
||||
.map(language => ({ code: language.code, short: language.short, label: language.label }))
|
||||
@ -375,7 +384,11 @@ function getChannelPage({ sourceId = null, includeHidden = false, userId = null,
|
||||
const filtered = serialized.filter(channel =>
|
||||
(!languages.length || languages.includes(channel.language.code)) && matchesSearch(channel, q)
|
||||
);
|
||||
const channels = filtered.map(channel => {
|
||||
// Catalog loading must stay bounded: both enrichers perform synchronous SQLite work,
|
||||
// and EPG matching is especially expensive against large XMLTV catalogs. Enrich only
|
||||
// a genuinely targeted, bounded search; base projection data remains usable otherwise.
|
||||
const shouldEnrich = Boolean(String(q).trim()) && filtered.length <= MAX_SYNCHRONOUS_ENRICHMENTS;
|
||||
const channels = !shouldEnrich ? filtered : filtered.map(channel => {
|
||||
const match = standardCatalog.findMatch(database, channel);
|
||||
if (match) {
|
||||
channel.standardName = match.name;
|
||||
@ -472,6 +485,7 @@ module.exports = {
|
||||
identityCandidatePairs,
|
||||
normalizeChannelName,
|
||||
markProjectionBuilding,
|
||||
MAX_SYNCHRONOUS_ENRICHMENTS,
|
||||
matchesSearch,
|
||||
projectCatalog,
|
||||
projectionState,
|
||||
|
||||
Reference in New Issue
Block a user