add timer to watchlist/wishlist
This commit is contained in:
parent
aba4129193
commit
9575d8983c
2 changed files with 170 additions and 12 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
Loading…
Reference in a new issue