add timer to watchlist/wishlist

This commit is contained in:
Broque Thomas 2025-11-26 12:14:08 -08:00
parent aba4129193
commit 9575d8983c
2 changed files with 170 additions and 12 deletions

View file

@ -233,6 +233,7 @@ wishlist_auto_processor = ThreadPoolExecutor(max_workers=1, thread_name_prefix="
wishlist_auto_timer = None # threading.Timer for scheduling next auto-processing
wishlist_auto_processing = False # Flag to prevent concurrent auto-processing
wishlist_auto_processing_timestamp = 0 # Timestamp when processing started (for stuck detection)
wishlist_next_run_time = 0 # Timestamp when next auto-processing is scheduled (for countdown display)
wishlist_timer_lock = threading.Lock() # Thread safety for timer operations
# --- Automatic Watchlist Scanning Infrastructure ---
@ -240,6 +241,7 @@ wishlist_timer_lock = threading.Lock() # Thread safety for timer operations
watchlist_auto_timer = None # threading.Timer for scheduling next auto-scanning
watchlist_auto_scanning = False # Flag to prevent concurrent auto-scanning
watchlist_auto_scanning_timestamp = 0 # Timestamp when scanning started (for stuck detection)
watchlist_next_run_time = 0 # Timestamp when next auto-scanning is scheduled (for countdown display)
watchlist_timer_lock = threading.Lock() # Thread safety for timer operations
# --- Shared Transfer Data Cache ---
@ -7608,10 +7610,11 @@ def stop_wishlist_auto_processing():
def schedule_next_wishlist_processing():
"""Schedule next automatic wishlist processing in 30 minutes."""
global wishlist_auto_timer
global wishlist_auto_timer, wishlist_next_run_time
with wishlist_timer_lock:
print("⏰ Scheduling next automatic wishlist processing in 30 minutes")
wishlist_next_run_time = time.time() + 1800.0 # Set timestamp for countdown display
wishlist_auto_timer = threading.Timer(1800.0, _process_wishlist_automatically) # 30 minutes (1800 seconds)
wishlist_auto_timer.daemon = True
wishlist_auto_timer.start()
@ -7973,10 +7976,17 @@ def get_wishlist_stats():
total_count = singles_count + albums_count
# Calculate time until next auto-processing
next_run_in_seconds = 0
with wishlist_timer_lock:
if wishlist_next_run_time > 0:
next_run_in_seconds = max(0, int(wishlist_next_run_time - time.time()))
return jsonify({
"singles": singles_count,
"albums": albums_count,
"total": total_count
"total": total_count,
"next_run_in_seconds": next_run_in_seconds
})
except Exception as e:
@ -14777,7 +14787,18 @@ def get_watchlist_count():
try:
database = get_database()
count = database.get_watchlist_count()
return jsonify({"success": True, "count": count})
# Calculate time until next auto-scanning
next_run_in_seconds = 0
with watchlist_timer_lock:
if watchlist_next_run_time > 0:
next_run_in_seconds = max(0, int(watchlist_next_run_time - time.time()))
return jsonify({
"success": True,
"count": count,
"next_run_in_seconds": next_run_in_seconds
})
except Exception as e:
print(f"Error getting watchlist count: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@ -15445,7 +15466,7 @@ watchlist_scan_state = {
def start_watchlist_auto_scanning():
"""Start automatic watchlist scanning with 5-minute initial delay (Timer-based like wishlist)"""
global watchlist_auto_timer
global watchlist_auto_timer, watchlist_next_run_time
print("🚀 [Auto-Watchlist] Initializing automatic watchlist scanning...")
@ -15455,6 +15476,7 @@ def start_watchlist_auto_scanning():
watchlist_auto_timer.cancel()
print("🔄 Starting automatic watchlist scanning system (5 minute initial delay)")
watchlist_next_run_time = time.time() + 300.0 # Set timestamp for countdown display
watchlist_auto_timer = threading.Timer(300.0, _process_watchlist_scan_automatically) # 5 minutes
watchlist_auto_timer.daemon = True
watchlist_auto_timer.start()
@ -15475,10 +15497,11 @@ def stop_watchlist_auto_scanning():
def schedule_next_watchlist_scan():
"""Schedule next automatic watchlist scan in 24 hours."""
global watchlist_auto_timer
global watchlist_auto_timer, watchlist_next_run_time
with watchlist_timer_lock:
print("⏰ Scheduling next automatic watchlist scan in 24 hours")
watchlist_next_run_time = time.time() + 86400.0 # Set timestamp for countdown display
watchlist_auto_timer = threading.Timer(86400.0, _process_watchlist_scan_automatically) # 24 hours
watchlist_auto_timer.daemon = True
watchlist_auto_timer.start()
@ -15500,7 +15523,8 @@ def _process_watchlist_scan_automatically():
if is_wishlist_actually_processing():
print("🎵 Wishlist processing in progress, rescheduling watchlist scan for 10 minutes from now")
# Smart retry: don't wait 24 hours, just wait 10 minutes and try again
global watchlist_auto_timer
global watchlist_auto_timer, watchlist_next_run_time
watchlist_next_run_time = time.time() + 600.0 # Set timestamp for countdown display
watchlist_auto_timer = threading.Timer(600.0, _process_watchlist_scan_automatically) # 10 minutes
watchlist_auto_timer.daemon = True
watchlist_auto_timer.start()

View file

@ -30,6 +30,8 @@ let dbUpdateStatusInterval = null;
let qualityScannerStatusInterval = null;
let duplicateCleanerStatusInterval = null;
let wishlistCountInterval = null;
let wishlistCountdownInterval = null; // Countdown timer for wishlist overview modal
let watchlistCountdownInterval = null; // Countdown timer for watchlist overview modal
// --- Add these globals for the Sync Page ---
let spotifyPlaylists = [];
@ -1275,12 +1277,29 @@ function onAudioCanPlay() {
function formatTime(seconds) {
// Format seconds as MM:SS
if (!seconds || !isFinite(seconds)) return '0:00';
const minutes = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${minutes}:${secs.toString().padStart(2, '0')}`;
}
function formatCountdownTime(seconds) {
// Format seconds as countdown timer (e.g., "24m 13s", "2h 15m", "23h 59m")
if (!seconds || seconds <= 0) return '';
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hours > 0) {
return `${hours}h ${minutes}m`;
} else if (minutes > 0) {
return `${minutes}m ${secs}s`;
} else {
return `${secs}s`;
}
}
// ===============================
// AUDIO FORMAT SUPPORT DETECTION
// ===============================
@ -4859,6 +4878,11 @@ async function openWishlistOverviewModal() {
const cycleData = await cycleResponse.json();
const currentCycle = cycleData.cycle || 'albums';
// Format countdown timer
const nextRunSeconds = statsData.next_run_in_seconds || 0;
const countdownText = formatCountdownTime(nextRunSeconds);
const nextCycleText = currentCycle === 'albums' ? 'Albums/EPs' : 'Singles';
modal.innerHTML = `
<div class="modal-container playlist-modal">
<div class="playlist-modal-header">
@ -4866,7 +4890,7 @@ async function openWishlistOverviewModal() {
<h2>🎵 Wishlist Overview</h2>
<div class="playlist-quick-info">
<span class="playlist-track-count">${total} Total Tracks</span>
<span class="playlist-owner">Next Auto: ${currentCycle === 'albums' ? 'Albums/EPs' : 'Singles'}</span>
<span class="playlist-owner" id="wishlist-next-auto-timer">Next Auto: ${nextCycleText}${countdownText ? ' in ' + countdownText : ''}</span>
</div>
</div>
<span class="playlist-modal-close" onclick="closeWishlistOverviewModal()">×</span>
@ -4925,6 +4949,9 @@ async function openWishlistOverviewModal() {
modal.style.display = 'flex';
hideLoadingOverlay();
// Start countdown timer update interval
startWishlistCountdownTimer(currentCycle, nextRunSeconds);
} catch (error) {
console.error('Error opening wishlist overview:', error);
showToast(`Failed to load wishlist: ${error.message}`, 'error');
@ -4932,8 +4959,59 @@ async function openWishlistOverviewModal() {
}
}
function startWishlistCountdownTimer(currentCycle, initialSeconds) {
// Clear any existing interval
if (wishlistCountdownInterval) {
clearInterval(wishlistCountdownInterval);
}
let remainingSeconds = initialSeconds;
const nextCycleText = currentCycle === 'albums' ? 'Albums/EPs' : 'Singles';
wishlistCountdownInterval = setInterval(async () => {
remainingSeconds--;
if (remainingSeconds <= 0) {
// Timer expired, fetch fresh data
try {
const response = await fetch('/api/wishlist/stats');
const data = await response.json();
remainingSeconds = data.next_run_in_seconds || 0;
// Also update cycle in case it changed
const cycleResponse = await fetch('/api/wishlist/cycle');
const cycleData = await cycleResponse.json();
const newCycle = cycleData.cycle || 'albums';
const newCycleText = newCycle === 'albums' ? 'Albums/EPs' : 'Singles';
const timerElement = document.getElementById('wishlist-next-auto-timer');
if (timerElement) {
const countdownText = formatCountdownTime(remainingSeconds);
timerElement.textContent = `Next Auto: ${newCycleText}${countdownText ? ' in ' + countdownText : ''}`;
}
} catch (error) {
console.debug('Error updating wishlist countdown:', error);
}
} else {
// Update the display
const timerElement = document.getElementById('wishlist-next-auto-timer');
if (timerElement) {
const countdownText = formatCountdownTime(remainingSeconds);
timerElement.textContent = `Next Auto: ${nextCycleText}${countdownText ? ' in ' + countdownText : ''}`;
}
}
}, 1000); // Update every second
}
function closeWishlistOverviewModal() {
console.log('🚪 closeWishlistOverviewModal() called');
// Stop countdown timer
if (wishlistCountdownInterval) {
clearInterval(wishlistCountdownInterval);
wishlistCountdownInterval = null;
}
const modal = document.getElementById('wishlist-overview-modal');
console.log('Modal element:', modal);
if (modal) {
@ -20311,11 +20389,16 @@ async function updateWatchlistButtonCount() {
try {
const response = await fetch('/api/watchlist/count');
const data = await response.json();
if (data.success) {
const watchlistButton = document.getElementById('watchlist-button');
if (watchlistButton) {
// Format countdown for button tooltip (optional enhancement)
const countdownText = data.next_run_in_seconds ? formatCountdownTime(data.next_run_in_seconds) : '';
watchlistButton.textContent = `👁️ Watchlist (${data.count})`;
if (countdownText) {
watchlistButton.title = `Next auto-scan in ${countdownText}`;
}
}
}
} catch (error) {
@ -20404,7 +20487,11 @@ async function showWatchlistModal() {
const statusResponse = await fetch('/api/watchlist/scan/status');
const statusData = await statusResponse.json();
const scanStatus = statusData.success ? statusData.status : 'idle';
// Format countdown timer
const nextRunSeconds = countData.next_run_in_seconds || 0;
const countdownText = formatCountdownTime(nextRunSeconds);
// Build modal content
modal.innerHTML = `
<div class="modal-container playlist-modal">
@ -20413,6 +20500,7 @@ async function showWatchlistModal() {
<h2>👁 Watchlist</h2>
<div class="playlist-quick-info">
<span class="playlist-track-count">${countData.count} artist${countData.count !== 1 ? 's' : ''}</span>
<span class="playlist-owner" id="watchlist-next-auto-timer">Next Auto${countdownText ? ': ' + countdownText : ''}</span>
</div>
<div class="playlist-modal-sync-status" id="watchlist-scan-status" style="display: ${scanStatus !== 'idle' ? 'flex' : 'none'}; flex-direction: column; align-items: center;">
<!-- Live Visual Activity Display -->
@ -20549,21 +20637,67 @@ async function showWatchlistModal() {
// Show modal
modal.style.display = 'flex';
// Start countdown timer update interval
startWatchlistCountdownTimer(nextRunSeconds);
// Start polling for scan status if scanning
if (scanStatus === 'scanning') {
pollWatchlistScanStatus();
}
} catch (error) {
console.error('Error showing watchlist modal:', error);
}
}
function startWatchlistCountdownTimer(initialSeconds) {
// Clear any existing interval
if (watchlistCountdownInterval) {
clearInterval(watchlistCountdownInterval);
}
let remainingSeconds = initialSeconds;
watchlistCountdownInterval = setInterval(async () => {
remainingSeconds--;
if (remainingSeconds <= 0) {
// Timer expired, fetch fresh data
try {
const response = await fetch('/api/watchlist/count');
const data = await response.json();
remainingSeconds = data.next_run_in_seconds || 0;
const timerElement = document.getElementById('watchlist-next-auto-timer');
if (timerElement) {
const countdownText = formatCountdownTime(remainingSeconds);
timerElement.textContent = `Next Auto${countdownText ? ': ' + countdownText : ''}`;
}
} catch (error) {
console.debug('Error updating watchlist countdown:', error);
}
} else {
// Update the display
const timerElement = document.getElementById('watchlist-next-auto-timer');
if (timerElement) {
const countdownText = formatCountdownTime(remainingSeconds);
timerElement.textContent = `Next Auto${countdownText ? ': ' + countdownText : ''}`;
}
}
}, 1000); // Update every second
}
/**
* Close watchlist modal
*/
function closeWatchlistModal() {
// Stop countdown timer
if (watchlistCountdownInterval) {
clearInterval(watchlistCountdownInterval);
watchlistCountdownInterval = null;
}
const modal = document.getElementById('watchlist-modal');
if (modal) {
modal.style.display = 'none';