From c358c794ece76496d4daaad1db808064f19625d2 Mon Sep 17 00:00:00 2001 From: tapframe Date: Fri, 3 Oct 2025 16:56:58 +0530 Subject: [PATCH] dedup fix localscraper --- src/services/localScraperService.ts | 47 +++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/services/localScraperService.ts b/src/services/localScraperService.ts index a3aceca..e1ee92b 100644 --- a/src/services/localScraperService.ts +++ b/src/services/localScraperService.ts @@ -81,6 +81,8 @@ class LocalScraperService { private autoRefreshCompleted: boolean = false; private isRefreshing: boolean = false; private scraperSettingsCache: Record | null = null; + // Single-flight map to prevent duplicate concurrent runs per scraper+title + private inFlightByKey: Map> = new Map(); private constructor() { this.initialize(); @@ -876,10 +878,13 @@ class LocalScraperService { } logger.log('[LocalScraperService] Executing', enabledScrapers.length, 'scrapers for', type, tmdbId); - + + // Generate a lightweight request id for tracing + const requestId = `rs_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`; + // Execute each scraper for (const scraper of enabledScrapers) { - this.executeScraper(scraper, type, tmdbId, season, episode, callback); + this.executeScraper(scraper, type, tmdbId, season, episode, callback, requestId); } } @@ -890,7 +895,8 @@ class LocalScraperService { tmdbId: string, season?: number, episode?: number, - callback?: ScraperCallback + callback?: ScraperCallback, + requestId?: string ): Promise { try { const code = this.scraperCode.get(scraper.id); @@ -903,15 +909,32 @@ class LocalScraperService { // Load per-scraper settings const scraperSettings = await this.getScraperSettings(scraper.id); - // Create a sandboxed execution environment - const results = await this.executeSandboxed(code, { - tmdbId, - mediaType: type, - season, - episode, - scraperId: scraper.id, - settings: scraperSettings - }); + // Build single-flight key + const flightKey = `${scraper.id}|${type}|${tmdbId}|${season ?? ''}|${episode ?? ''}`; + + // Create a sandboxed execution environment with single-flight coalescing + let promise: Promise; + if (this.inFlightByKey.has(flightKey)) { + promise = this.inFlightByKey.get(flightKey)!; + } else { + promise = this.executeSandboxed(code, { + tmdbId, + mediaType: type, + season, + episode, + scraperId: scraper.id, + settings: scraperSettings, + requestId + }); + this.inFlightByKey.set(flightKey, promise); + // Clean up after settle; guard against races + promise.finally(() => { + const current = this.inFlightByKey.get(flightKey); + if (current === promise) this.inFlightByKey.delete(flightKey); + }).catch(() => {}); + } + + const results = await promise; // Convert results to Nuvio Stream format const streams = this.convertToStreams(results, scraper);