/** * AniWorld - Scan Manager Module * * Handles library scanning and progress overlay. * * Dependencies: constants.js, api-client.js, ui-utils.js */ var AniWorld = window.AniWorld || {}; AniWorld.ScanManager = (function() { 'use strict'; const API = AniWorld.Constants.API; const DEFAULTS = AniWorld.Constants.DEFAULTS; // State let scanTotalItems = 0; let lastScanData = null; /** * Initialize the scan manager */ function init() { bindEvents(); // Check scan status on page load checkActiveScanStatus(); } /** * Bind UI events */ function bindEvents() { const rescanBtn = document.getElementById('rescan-btn'); if (rescanBtn) { rescanBtn.addEventListener('click', rescanSeries); } // Click on rescan status indicator to reopen scan overlay const rescanStatus = document.getElementById('rescan-status'); if (rescanStatus) { rescanStatus.addEventListener('click', function(e) { e.stopPropagation(); console.log('Rescan status clicked'); reopenScanOverlay(); }); } } /** * Start a rescan of the series directory */ async function rescanSeries() { try { // Show the overlay immediately before making the API call showScanProgressOverlay({ directory: 'Starting scan...', total_items: 0 }); updateProcessStatus('rescan', true); const response = await AniWorld.ApiClient.post(API.ANIME_RESCAN, {}); if (!response) { removeScanProgressOverlay(); updateProcessStatus('rescan', false); return; } const data = await response.json(); // Debug logging console.log('Rescan response:', data); // Note: The scan progress will be updated via WebSocket events // The overlay will be closed when scan_completed is received if (data.success !== true) { removeScanProgressOverlay(); updateProcessStatus('rescan', false); AniWorld.UI.showToast('Rescan error: ' + data.message, 'error'); } } catch (error) { console.error('Rescan error:', error); removeScanProgressOverlay(); updateProcessStatus('rescan', false); AniWorld.UI.showToast('Failed to start rescan', 'error'); } } /** * Show the scan progress overlay * @param {Object} data - Scan started event data */ function showScanProgressOverlay(data) { // Remove existing overlay if present removeScanProgressOverlay(); // Store total items for progress calculation scanTotalItems = data?.total_items || 0; // Store last scan data for reopening lastScanData = data; // Create overlay element const overlay = document.createElement('div'); overlay.id = 'scan-progress-overlay'; overlay.className = 'scan-progress-overlay'; const totalDisplay = scanTotalItems > 0 ? scanTotalItems : '...'; overlay.innerHTML = '
' + '
' + '

' + '' + '' + 'Scanning Library' + '

' + '
' + '
' + '
' + '
' + '
' + '0 / ' + totalDisplay + ' directories' + '
' + '
' + '
' + '0' + 'Scanned' + '
' + '
' + '0' + 'Series Found' + '
' + '
' + '
' + 'Current:' + '' + AniWorld.UI.escapeHtml(data?.directory || 'Initializing...') + '' + '
' + '' + '
'; document.body.appendChild(overlay); // Add click-outside-to-close handler overlay.addEventListener('click', function(e) { // Only close if clicking the overlay background, not the container if (e.target === overlay) { removeScanProgressOverlay(); } }); // Trigger animation by adding visible class after a brief delay requestAnimationFrame(function() { overlay.classList.add('visible'); }); } /** * Update the scan progress overlay * @param {Object} data - Scan progress event data */ function updateScanProgressOverlay(data) { const overlay = document.getElementById('scan-progress-overlay'); if (!overlay) return; // Update total items if provided if (data.total_items && data.total_items > 0) { scanTotalItems = data.total_items; const totalCount = document.getElementById('scan-total-count'); if (totalCount) { totalCount.textContent = scanTotalItems; } } // Update progress bar const progressBar = document.getElementById('scan-progress-bar'); if (progressBar && scanTotalItems > 0 && data.directories_scanned !== undefined) { const percentage = Math.min(100, (data.directories_scanned / scanTotalItems) * 100); progressBar.style.width = percentage + '%'; } // Update current/total count display const currentCount = document.getElementById('scan-current-count'); if (currentCount && data.directories_scanned !== undefined) { currentCount.textContent = data.directories_scanned; } // Update directories count const dirCount = document.getElementById('scan-directories-count'); if (dirCount && data.directories_scanned !== undefined) { dirCount.textContent = data.directories_scanned; } // Update files/series count const filesCount = document.getElementById('scan-files-count'); if (filesCount && data.files_found !== undefined) { filesCount.textContent = data.files_found; } // Update current directory (truncate if too long) const currentPath = document.getElementById('scan-current-path'); if (currentPath && data.current_directory) { const maxLength = 50; let displayPath = data.current_directory; if (displayPath.length > maxLength) { displayPath = '...' + displayPath.slice(-maxLength + 3); } currentPath.textContent = displayPath; currentPath.title = data.current_directory; } } /** * Hide the scan progress overlay with completion summary * @param {Object} data - Scan completed event data */ function hideScanProgressOverlay(data) { const overlay = document.getElementById('scan-progress-overlay'); if (!overlay) return; const container = overlay.querySelector('.scan-progress-container'); if (container) { container.classList.add('completed'); } // Update title const titleText = overlay.querySelector('.scan-title-text'); if (titleText) { titleText.textContent = 'Scan Complete'; } // Complete the progress bar const progressBar = document.getElementById('scan-progress-bar'); if (progressBar) { progressBar.style.width = '100%'; } // Update final stats if (data) { const dirCount = document.getElementById('scan-directories-count'); if (dirCount && data.total_directories !== undefined) { dirCount.textContent = data.total_directories; } const filesCount = document.getElementById('scan-files-count'); if (filesCount && data.total_files !== undefined) { filesCount.textContent = data.total_files; } // Update progress text to show final count const currentCount = document.getElementById('scan-current-count'); const totalCount = document.getElementById('scan-total-count'); if (currentCount && data.total_directories !== undefined) { currentCount.textContent = data.total_directories; } if (totalCount && data.total_directories !== undefined) { totalCount.textContent = data.total_directories; } // Show elapsed time const elapsedTimeEl = document.getElementById('scan-elapsed-time'); const elapsedValueEl = document.getElementById('scan-elapsed-value'); if (elapsedTimeEl && elapsedValueEl && data.elapsed_seconds !== undefined) { elapsedValueEl.textContent = data.elapsed_seconds.toFixed(1) + 's'; elapsedTimeEl.classList.remove('hidden'); } // Update current directory to show completion message const currentPath = document.getElementById('scan-current-path'); if (currentPath) { currentPath.textContent = 'Scan finished successfully'; } } // Auto-dismiss after 3 seconds setTimeout(function() { removeScanProgressOverlay(); }, DEFAULTS.SCAN_AUTO_DISMISS); } /** * Remove the scan progress overlay from the DOM */ function removeScanProgressOverlay() { const overlay = document.getElementById('scan-progress-overlay'); if (overlay) { overlay.classList.remove('visible'); // Wait for fade out animation before removing setTimeout(function() { if (overlay.parentElement) { overlay.remove(); } }, 300); } } /** * Reopen the scan progress overlay if a scan is in progress */ async function reopenScanOverlay() { // Check if overlay already exists const existingOverlay = document.getElementById('scan-progress-overlay'); if (existingOverlay) { return; } // Check if scan is running via API try { const response = await AniWorld.ApiClient.get(API.ANIME_SCAN_STATUS); if (!response || !response.ok) { console.log('Could not fetch scan status'); return; } const data = await response.json(); console.log('Scan status for reopen:', data); if (data.is_scanning) { // A scan is in progress, show the overlay showScanProgressOverlay({ directory: data.directory, total_items: data.total_items }); // Update with current progress updateScanProgressOverlay({ directories_scanned: data.directories_scanned, files_found: data.directories_scanned, current_directory: data.current_directory, total_items: data.total_items }); } } catch (error) { console.error('Error checking scan status for reopen:', error); } } /** * Check if a scan is currently in progress */ async function checkActiveScanStatus() { try { const response = await AniWorld.ApiClient.get(API.ANIME_SCAN_STATUS); if (!response || !response.ok) { console.log('Could not fetch scan status, response:', response?.status); return; } const data = await response.json(); console.log('Scan status check result:', data); if (data.is_scanning) { console.log('Scan is active, updating UI indicators'); // Update the process status indicator updateProcessStatus('rescan', true); // Show the overlay showScanProgressOverlay({ directory: data.directory, total_items: data.total_items }); // Update with current progress updateScanProgressOverlay({ directories_scanned: data.directories_scanned, files_found: data.directories_scanned, current_directory: data.current_directory, total_items: data.total_items }); } else { console.log('No active scan detected'); updateProcessStatus('rescan', false); } } catch (error) { console.error('Error checking scan status:', error); } } /** * Update process status indicator * @param {string} processName - Process name (e.g., 'rescan', 'download') * @param {boolean} isRunning - Whether the process is running * @param {boolean} hasError - Whether there's an error */ function updateProcessStatus(processName, isRunning, hasError) { hasError = hasError || false; const statusElement = document.getElementById(processName + '-status'); if (!statusElement) { console.warn('Process status element not found: ' + processName + '-status'); return; } const statusDot = statusElement.querySelector('.status-dot'); if (!statusDot) { console.warn('Status dot not found in ' + processName + '-status element'); return; } // Remove all status classes statusDot.classList.remove('idle', 'running', 'error'); statusElement.classList.remove('running', 'error', 'idle'); // Capitalize process name for display const displayName = processName.charAt(0).toUpperCase() + processName.slice(1); if (hasError) { statusDot.classList.add('error'); statusElement.classList.add('error'); statusElement.title = displayName + ' error - click for details'; } else if (isRunning) { statusDot.classList.add('running'); statusElement.classList.add('running'); statusElement.title = displayName + ' is running...'; } else { statusDot.classList.add('idle'); statusElement.classList.add('idle'); statusElement.title = displayName + ' is idle'; } console.log('Process status updated: ' + processName + ' = ' + (isRunning ? 'running' : (hasError ? 'error' : 'idle'))); } // Public API return { init: init, rescanSeries: rescanSeries, showScanProgressOverlay: showScanProgressOverlay, updateScanProgressOverlay: updateScanProgressOverlay, hideScanProgressOverlay: hideScanProgressOverlay, removeScanProgressOverlay: removeScanProgressOverlay, reopenScanOverlay: reopenScanOverlay, checkActiveScanStatus: checkActiveScanStatus, updateProcessStatus: updateProcessStatus }; })();