mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
feat(epg): add indexing and cleanup for faster lookups and memory
Introduce a lowercase display name index (Map) to allow O(1) resolution from display name to channel id. Rebuild the index during merged-data rebuild and use it in channel lookups to avoid expensive full scans. Add a fallback partial-match pass only when the index lookup fails. Add automatic and manual cleanup of expired programs: - Add cleanupExpiredPrograms(hoursToKeep) to remove programs older than the specified number of hours from epgDataMerged. - Invoke cleanup after worker fetches (auto-cleanup with 24 hours). - Expose an IPC handler EPG_CLEANUP_EXPIRED to trigger cleanup on demand. Other changes: - Clear epgData.programs after merging to free memory. - Log merged data rebuild and cleanup counts for observability. These changes improve performance of channel resolution, reduce memory usage by pruning stale program entries, and provide a manual hook for on-demand maintenance.
This commit is contained in:
1 parent
fe8e907e9f
commit
a6ad3fce14
1 file changed
+75
-15
@@ -25,6 +25,9 @@ export default class EpgEvents {
|
||||
private static workers: Map<string, Worker> = new Map();
|
||||
private static readonly loggerLabel = '[EPG Events]';
|
||||
|
||||
// Display name index for O(1) lookups (lowercase name -> channel id)
|
||||
private static displayNameIndex: Map<string, string> = new Map();
|
||||
|
||||
/**
|
||||
* Bootstrap EPG events
|
||||
*/
|
||||
@@ -61,6 +64,14 @@ export default class EpgEvents {
|
||||
return await this.handleFetchEpg([url]);
|
||||
});
|
||||
|
||||
// Cleanup expired programs
|
||||
ipcMain.handle(
|
||||
'EPG_CLEANUP_EXPIRED',
|
||||
async (event, hoursToKeep?: number) => {
|
||||
return this.cleanupExpiredPrograms(hoursToKeep);
|
||||
}
|
||||
);
|
||||
|
||||
return ipcMain;
|
||||
}
|
||||
|
||||
@@ -187,6 +198,8 @@ export default class EpgEvents {
|
||||
message.stats
|
||||
);
|
||||
this.rebuildMergedData();
|
||||
// Auto-cleanup programs older than 24 hours
|
||||
this.cleanupExpiredPrograms(24);
|
||||
this.fetchedUrls.add(url);
|
||||
worker.terminate();
|
||||
this.workers.delete(url);
|
||||
@@ -249,10 +262,23 @@ export default class EpgEvents {
|
||||
*/
|
||||
private static rebuildMergedData(): void {
|
||||
this.epgDataMerged.clear();
|
||||
this.displayNameIndex.clear();
|
||||
|
||||
// Create channel lookup map for O(1) access
|
||||
const channelMap = new Map(this.epgData.channels.map((c) => [c.id, c]));
|
||||
|
||||
// Build display name index for fast lookups
|
||||
for (const channel of this.epgData.channels) {
|
||||
for (const name of channel.displayName) {
|
||||
if (name.value) {
|
||||
this.displayNameIndex.set(
|
||||
name.value.toLowerCase(),
|
||||
channel.id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Group programs by channel in a single pass
|
||||
const programsByChannel = new Map<string, EpgProgram[]>();
|
||||
|
||||
@@ -274,6 +300,14 @@ export default class EpgEvents {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Free memory - programs are now stored in epgDataMerged
|
||||
this.epgData.programs = [];
|
||||
|
||||
console.log(
|
||||
this.loggerLabel,
|
||||
`Merged data rebuilt: ${this.epgDataMerged.size} channels, ${this.displayNameIndex.size} display names indexed`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,22 +317,21 @@ export default class EpgEvents {
|
||||
// First try exact ID match
|
||||
let channelData = this.epgDataMerged.get(channelId);
|
||||
|
||||
// If not found, try to find by display name
|
||||
// If not found, try display name index (O(1) lookup)
|
||||
if (!channelData) {
|
||||
for (const [, channel] of this.epgDataMerged.entries()) {
|
||||
const displayNames = channel.displayName.map((d) =>
|
||||
d.value.toLowerCase()
|
||||
);
|
||||
if (
|
||||
displayNames.some(
|
||||
(name) =>
|
||||
name === channelId.toLowerCase() ||
|
||||
name.includes(channelId.toLowerCase()) ||
|
||||
channelId.toLowerCase().includes(name)
|
||||
)
|
||||
) {
|
||||
channelData = channel;
|
||||
break;
|
||||
const indexedId = this.displayNameIndex.get(channelId.toLowerCase());
|
||||
if (indexedId) {
|
||||
channelData = this.epgDataMerged.get(indexedId);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: partial match (only if index lookup failed)
|
||||
if (!channelData) {
|
||||
const searchTerm = channelId.toLowerCase();
|
||||
for (const [name, id] of this.displayNameIndex.entries()) {
|
||||
if (name.includes(searchTerm) || searchTerm.includes(name)) {
|
||||
channelData = this.epgDataMerged.get(id);
|
||||
if (channelData) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,12 +350,39 @@ export default class EpgEvents {
|
||||
return channels.slice(skip, skip + limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up expired programs (older than specified hours)
|
||||
* Call this periodically to free memory from stale EPG data
|
||||
*/
|
||||
static cleanupExpiredPrograms(hoursToKeep = 24): number {
|
||||
const cutoff = new Date(
|
||||
Date.now() - hoursToKeep * 60 * 60 * 1000
|
||||
).toISOString();
|
||||
let removedCount = 0;
|
||||
|
||||
for (const channel of this.epgDataMerged.values()) {
|
||||
const originalLength = channel.programs.length;
|
||||
channel.programs = channel.programs.filter((p) => p.stop > cutoff);
|
||||
removedCount += originalLength - channel.programs.length;
|
||||
}
|
||||
|
||||
if (removedCount > 0) {
|
||||
console.log(
|
||||
this.loggerLabel,
|
||||
`Cleaned up ${removedCount} expired programs (older than ${hoursToKeep}h)`
|
||||
);
|
||||
}
|
||||
|
||||
return removedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all EPG data
|
||||
*/
|
||||
static clearEpgData(): void {
|
||||
this.epgData = { channels: [], programs: [] };
|
||||
this.epgDataMerged.clear();
|
||||
this.displayNameIndex.clear();
|
||||
this.fetchedUrls.clear();
|
||||
|
||||
// Terminate all workers
|
||||
|
||||
Reference in new issue
Block a user