(arg = {})
| 417 | } |
| 418 | |
| 419 | async backfillVideoMetadata(arg = {}) { |
| 420 | const opts = typeof arg === 'number' ? { timeLimit: arg } : arg; |
| 421 | const timeLimit = opts.timeLimit ?? 5 * 60 * 1000; |
| 422 | const trigger = opts.trigger ?? 'scheduled'; |
| 423 | |
| 424 | if (this._backfillRunning) { |
| 425 | logger.info({ trigger }, 'Backfill already running, skipping'); |
| 426 | return { skipped: true, reason: 'already-running' }; |
| 427 | } |
| 428 | this._backfillRunning = true; |
| 429 | |
| 430 | const startTime = Date.now(); |
| 431 | const startedAtIso = new Date(startTime).toISOString(); |
| 432 | const logProgress = (message) => { |
| 433 | const elapsed = Math.round((Date.now() - startTime) / 1000); |
| 434 | logger.info({ elapsed, context: 'backfill' }, message); |
| 435 | }; |
| 436 | |
| 437 | let totalProcessed = 0; |
| 438 | let totalUpdated = 0; |
| 439 | let totalRemoved = 0; |
| 440 | let fileMapSize = 0; |
| 441 | let result; |
| 442 | |
| 443 | try { |
| 444 | // Emit inside the try so a synchronous emit failure still triggers the |
| 445 | // finally block and clears the lock. |
| 446 | messageEmitter.emitMessage('broadcast', null, 'server', 'rescanStatus', { |
| 447 | running: true, |
| 448 | trigger |
| 449 | }); |
| 450 | |
| 451 | logProgress('Starting video metadata backfill...'); |
| 452 | const outputDir = configModule.directoryPath; |
| 453 | |
| 454 | if (!outputDir) { |
| 455 | logger.info('No YouTube output directory configured, skipping backfill'); |
| 456 | return; |
| 457 | } |
| 458 | |
| 459 | // Check time limit before expensive operations |
| 460 | const checkTimeLimit = () => { |
| 461 | if (Date.now() - startTime > timeLimit) { |
| 462 | throw new Error(`Time limit exceeded (${timeLimit / 1000}s)`); |
| 463 | } |
| 464 | }; |
| 465 | |
| 466 | // First, scan filesystem for all video files |
| 467 | logProgress('Scanning filesystem for video files...'); |
| 468 | const { fileMap, duplicates } = await this.scanForVideoFiles(outputDir); |
| 469 | fileMapSize = fileMap.size; |
| 470 | logProgress(`Found ${fileMap.size} video files on disk`); |
| 471 | |
| 472 | |
| 473 | if (duplicates.size > 0) { |
| 474 | logger.warn({ duplicateCount: duplicates.size }, 'Found videos with duplicate files'); |
| 475 | for (const [youtubeId, paths] of duplicates.entries()) { |
| 476 | logger.warn({ youtubeId, fileCount: paths.length, paths }, 'Duplicate video files found'); |
no test coverage detected