440 lines
16 KiB
JavaScript
440 lines
16 KiB
JavaScript
/**
|
|
* 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 =
|
|
'<div class="scan-progress-container">' +
|
|
'<div class="scan-progress-header">' +
|
|
'<h3>' +
|
|
'<span class="scan-progress-spinner"></span>' +
|
|
'<i class="fas fa-check-circle scan-completed-icon"></i>' +
|
|
'<span class="scan-title-text">Scanning Library</span>' +
|
|
'</h3>' +
|
|
'</div>' +
|
|
'<div class="scan-progress-bar-container">' +
|
|
'<div class="scan-progress-bar" id="scan-progress-bar" style="width: 0%"></div>' +
|
|
'</div>' +
|
|
'<div class="scan-progress-text" id="scan-progress-text">' +
|
|
'<span id="scan-current-count">0</span> / <span id="scan-total-count">' + totalDisplay + '</span> directories' +
|
|
'</div>' +
|
|
'<div class="scan-progress-stats">' +
|
|
'<div class="scan-stat">' +
|
|
'<span class="scan-stat-value" id="scan-directories-count">0</span>' +
|
|
'<span class="scan-stat-label">Scanned</span>' +
|
|
'</div>' +
|
|
'<div class="scan-stat">' +
|
|
'<span class="scan-stat-value" id="scan-files-count">0</span>' +
|
|
'<span class="scan-stat-label">Series Found</span>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div class="scan-current-directory" id="scan-current-directory">' +
|
|
'<span class="scan-current-directory-label">Current:</span>' +
|
|
'<span id="scan-current-path">' + AniWorld.UI.escapeHtml(data?.directory || 'Initializing...') + '</span>' +
|
|
'</div>' +
|
|
'<div class="scan-elapsed-time hidden" id="scan-elapsed-time">' +
|
|
'<i class="fas fa-clock"></i>' +
|
|
'<span id="scan-elapsed-value">0.0s</span>' +
|
|
'</div>' +
|
|
'</div>';
|
|
|
|
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
|
|
};
|
|
})();
|