Skip to content

Fix streamed EPG ingestion and atomic profile saves - #185

Merged
cbulock merged 1 commit into
mainfrom
codex/remaining-guide-work
Sep 6, 2026
Merged

Fix streamed EPG ingestion and atomic profile saves#185
cbulock merged 1 commit into
mainfrom
codex/remaining-guide-work

Conversation

@cbulock

@cbulock cbulock commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Summary

  • stream XMLTV and gzip inputs with bounded parsing and cache memory
  • index guide programmes for queries without reparsing the full guide
  • save output-profile edits atomically through one HTTP or MCP operation

Fixes #172
Fixes #179

Validation

  • 72 focused tests
  • ESLint on changed files
  • git diff --check

Copilot AI lite review requested due to automatic review settings September 6, 2026 20:17
Comment thread server/canonical.js

// The authoring UI may change profile metadata, canonical channel choices, and
// output entries together. Save them atomically, then rebuild the guide once.
router.put('/api/output-profiles/:slug', requireAuth, async (req, res) => {
@cbulock
cbulock merged commit 269d582 into main Sep 6, 2026
12 of 15 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There are confirmed performance/operational issues (notably getGuideData allocating all programmes when unscoped, plus cache TTL timer behavior on dynamic TTL updates) and the new atomic save path needs structured error identifiers to preserve row-level feedback.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR addresses two core scalability/usability issues in the IPTV Proxy: (1) bounded-memory EPG/XMLTV ingestion (including gzip streams) with programme indexing for guide queries, and (2) atomic “Save Output Profile” behavior via a single HTTP/MCP operation to avoid repeated refresh cycles.

Changes:

  • Add streaming XMLTV decoding/parsing utilities with decoded-size limits and gzip support, and use them in EPG ingestion.
  • Build and use an in-memory programme index to answer /api/guide (and MCP guide reads) without reparsing the full merged XMLTV document.
  • Introduce an atomic output-profile save API (HTTP PUT /api/output-profiles/:slug) and MCP tool (save_output_profile), and update the admin UI + tests accordingly.
File summaries
File Description
test/unit/xmltv-stream.test.js Adds unit coverage for chunked parsing, gzip decoding, size limits, and a representative “large feed” scenario.
test/unit/cache-manager.test.js Adds coverage for byte-bounded cache eviction behavior.
test/integration/output-profile-routes.test.js Verifies atomic save behavior across profile metadata, canonical edits, and output entries.
test/integration/mcp.test.js Adds integration coverage for the new save_output_profile MCP tool.
server/xmltv-stream.js Introduces streaming XMLTV read/limit utilities (decodeXmltvStream, readXmltvRecords).
server/mcp.js Adds save_output_profile MCP tool and surfaces it in workflow metadata.
server/epg.js Switches ingestion to streamed XMLTV reading, adds byte-bounded EPG cache, and introduces programme indexing for guide queries.
server/canonical.js Adds atomic HTTP save route for output profile edits + refresh/invalidate behavior.
libs/output-profile-service.js Implements transactional saveOutputProfile() to apply UI edits atomically.
libs/cache-manager.js Adds max-bytes cache bounding and background expiry purging.
admin/src/App.vue Replaces many sequential PATCH calls with a single atomic PUT save request.
Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 5
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread admin/src/App.vue
Comment on lines +2481 to 2484
const json = await response.json();
if (!response.ok) {
throw new Error(buildApiErrorMessage(json, 'Failed to save output profile'));
}
Comment thread libs/cache-manager.js
Comment on lines +65 to +70
// Expiry must not depend on a later read of the same cache key. The
// timer is unref'd so a cache never keeps the service/test process alive.
this.cleanupTimer =
this.ttl > 0
? setInterval(() => this.purgeExpired(), Math.max(1000, Math.min(this.ttl, 60000))).unref()
: null;
Comment thread server/canonical.js
Comment on lines +399 to +410
if (result?.error === 'canonical-not-found') {
return res.status(404).json({ error: `Canonical channel not found: ${result.canonicalId}` });
}
if (result?.error === 'binding-not-found' || result?.error === 'guide-binding-not-found') {
return res.status(404).json({ error: `Channel binding not found for canonical channel ${result.canonicalId}` });
}
if (result?.error === 'entry-not-found') {
return res.status(404).json({ error: `Output profile entry not found for canonical channel ${result.canonicalId}` });
}
if (result?.error === 'invalid-entry') {
return res.status(400).json({ error: `Invalid output profile entry for canonical channel ${result.canonicalId}` });
}
Comment thread server/epg.js
Comment on lines +195 to +197
let programmes = tvgId
? guideProgrammeIndex.get(tvgId) || []
: Array.from(guideProgrammeIndex.values()).flat();
Comment thread server/xmltv-stream.js
Comment on lines +62 to +66
const closeTag = `</${activeType}>`;
const openingEnd = buffer.indexOf('>');
const selfClosing = openingEnd !== -1 && /\/\s*$/.test(buffer.slice(0, openingEnd));
const closeIndex = selfClosing ? -1 : buffer.toLowerCase().indexOf(closeTag);
const endIndex = selfClosing ? openingEnd + 1 : closeIndex === -1 ? -1 : closeIndex + closeTag.length;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P2] Save channel edits without downloading every EPG once per edited field [P2] Stream XMLTV ingestion and bound guide/cache memory

3 participants