diff --git a/src/server/app.py b/src/server/app.py index 6086b0b..b694c66 100644 --- a/src/server/app.py +++ b/src/server/app.py @@ -69,17 +69,41 @@ RESCAN_LOCK = "rescan" DOWNLOAD_LOCK = "download" CLEANUP_LOCK = "cleanup" +# Simple in-memory process lock system +_active_locks = {} + def is_process_running(lock_name): - """Placeholder function for process lock checking.""" - return False + """Check if a process is currently running (locked).""" + return lock_name in _active_locks + +def acquire_lock(lock_name, locked_by="system"): + """Acquire a process lock.""" + if lock_name in _active_locks: + raise ProcessLockError(f"Process {lock_name} is already running") + _active_locks[lock_name] = { + 'locked_by': locked_by, + 'timestamp': datetime.now() + } + +def release_lock(lock_name): + """Release a process lock.""" + if lock_name in _active_locks: + del _active_locks[lock_name] def with_process_lock(lock_name, timeout_minutes=30): - """Placeholder decorator for process locking.""" + """Decorator for process locking.""" def decorator(f): from functools import wraps @wraps(f) def decorated_function(*args, **kwargs): - return f(*args, **kwargs) + # Extract locked_by from kwargs if provided + locked_by = kwargs.pop('_locked_by', 'system') + + try: + acquire_lock(lock_name, locked_by) + return f(*args, **kwargs) + finally: + release_lock(lock_name) return decorated_function return decorator diff --git a/src/server/web/static/css/styles.css b/src/server/web/static/css/styles.css index adfe4b3..700dd1c 100644 --- a/src/server/web/static/css/styles.css +++ b/src/server/web/static/css/styles.css @@ -869,10 +869,6 @@ body { .process-status { gap: 4px; } - - .status-text { - font-size: 0.8rem; - } } @media (max-width: 768px) { @@ -1441,16 +1437,22 @@ body { } .status-indicator i { - font-size: 12px; + font-size: 24px; /* 2x bigger: 12px -> 24px */ + transition: all var(--animation-duration-normal) var(--animation-easing-standard); } -.status-text { - font-weight: 500; - white-space: nowrap; - flex-shrink: 0; - margin-left: 2px; +/* Rescan icon specific styling */ +#rescan-status i { + color: var(--color-text-disabled); /* Gray when idle */ } +#rescan-status.running i { + color: #22c55e; /* Green when running */ + animation: iconPulse 2s infinite; +} + +/* Status text removed - using tooltips only */ + .status-dot { width: 8px; height: 8px; @@ -1472,7 +1474,6 @@ body { } @keyframes pulse { - 0%, 100% { opacity: 1; @@ -1485,6 +1486,19 @@ body { } } +@keyframes iconPulse { + 0%, + 100% { + opacity: 1; + transform: scale(1) rotate(0deg); + } + + 50% { + opacity: 0.7; + transform: scale(1.1) rotate(180deg); + } +} + /* Process status in mobile view */ @media (max-width: 768px) { .process-status { @@ -1499,12 +1513,8 @@ body { gap: 4px; } - .status-text { - display: none; - } - .status-indicator i { - font-size: 14px; + font-size: 20px; /* Maintain 2x scale for mobile: was 14px -> 20px */ } } diff --git a/src/server/web/static/js/app.js b/src/server/web/static/js/app.js index dc03ced..e90f2ae 100644 --- a/src/server/web/static/js/app.js +++ b/src/server/web/static/js/app.js @@ -896,30 +896,45 @@ class AniWorldApp { const statusDot = statusElement.querySelector('.status-dot'); if (!statusDot) return; - // Remove all status classes + // Remove all status classes from both dot and element 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.title = `${processName} error - click for details`; + statusElement.classList.add('error'); + statusElement.title = `${displayName} error - click for details`; } else if (isRunning) { statusDot.classList.add('running'); - statusElement.title = `${processName} is running...`; + statusElement.classList.add('running'); + statusElement.title = `${displayName} is running...`; } else { statusDot.classList.add('idle'); - statusElement.title = `${processName} is idle`; + statusElement.classList.add('idle'); + statusElement.title = `${displayName} is idle`; } } async checkProcessLocks() { try { const response = await this.makeAuthenticatedRequest('/api/process/locks/status'); - if (!response) return; + if (!response) { + // If no response, set status as idle + this.updateProcessStatus('rescan', false); + this.updateProcessStatus('download', false); + return; + } // Check if response is actually JSON and not HTML (login page) const contentType = response.headers.get('content-type'); if (!contentType || !contentType.includes('application/json')) { console.warn('Process locks API returned non-JSON response, likely authentication issue'); + // Set status as idle if we can't get proper response + this.updateProcessStatus('rescan', false); + this.updateProcessStatus('download', false); return; } @@ -935,25 +950,40 @@ class AniWorldApp { if (rescanBtn) { if (locks.rescan?.is_locked) { rescanBtn.disabled = true; - rescanBtn.querySelector('span').textContent = 'Scanning...'; + const span = rescanBtn.querySelector('span'); + if (span) span.textContent = 'Scanning...'; } else { rescanBtn.disabled = false; - rescanBtn.querySelector('span').textContent = 'Rescan'; + const span = rescanBtn.querySelector('span'); + if (span) span.textContent = 'Rescan'; } } + } else { + // If API returns error, set status as idle + console.warn('Process locks API returned error:', data.error); + this.updateProcessStatus('rescan', false); + this.updateProcessStatus('download', false); } } catch (error) { console.error('Error checking process locks:', error); + // On error, set status as idle + this.updateProcessStatus('rescan', false); + this.updateProcessStatus('download', false); } } startProcessStatusMonitoring() { + // Initial check on page load + this.checkProcessLocks(); + // Check process status every 5 seconds setInterval(() => { if (this.isConnected) { this.checkProcessLocks(); } }, 5000); + + console.log('Process status monitoring started'); } async showConfigModal() { diff --git a/src/server/web/templates/base/index.html b/src/server/web/templates/base/index.html index 843a3b5..57ed13a 100644 --- a/src/server/web/templates/base/index.html +++ b/src/server/web/templates/base/index.html @@ -24,14 +24,12 @@