move wishlist processing to backend
This commit is contained in:
parent
ab9be91a90
commit
697a255047
2 changed files with 274 additions and 143 deletions
201
web_server.py
201
web_server.py
|
|
@ -120,6 +120,13 @@ download_tasks = {} # task_id -> task state dict
|
||||||
download_batches = {} # batch_id -> {queue, active_count, max_concurrent}
|
download_batches = {} # batch_id -> {queue, active_count, max_concurrent}
|
||||||
tasks_lock = threading.Lock()
|
tasks_lock = threading.Lock()
|
||||||
|
|
||||||
|
# --- Automatic Wishlist Processing Infrastructure ---
|
||||||
|
# Server-side timer system for automatic wishlist processing (replaces client-side JavaScript timers)
|
||||||
|
wishlist_auto_processor = ThreadPoolExecutor(max_workers=1, thread_name_prefix="WishlistAutoProcessor")
|
||||||
|
wishlist_auto_timer = None # threading.Timer for scheduling next auto-processing
|
||||||
|
wishlist_auto_processing = False # Flag to prevent concurrent auto-processing
|
||||||
|
wishlist_timer_lock = threading.Lock() # Thread safety for timer operations
|
||||||
|
|
||||||
# --- Shared Transfer Data Cache ---
|
# --- Shared Transfer Data Cache ---
|
||||||
# Cache transfer data to avoid hammering the Soulseek API with multiple concurrent modals
|
# Cache transfer data to avoid hammering the Soulseek API with multiple concurrent modals
|
||||||
transfer_data_cache = {
|
transfer_data_cache = {
|
||||||
|
|
@ -3384,6 +3391,139 @@ def start_simple_background_monitor():
|
||||||
monitor_thread.daemon = True
|
monitor_thread.daemon = True
|
||||||
monitor_thread.start()
|
monitor_thread.start()
|
||||||
|
|
||||||
|
# ===============================
|
||||||
|
# == AUTOMATIC WISHLIST PROCESSING ==
|
||||||
|
# ===============================
|
||||||
|
|
||||||
|
def start_wishlist_auto_processing():
|
||||||
|
"""Start automatic wishlist processing with 1-minute initial delay."""
|
||||||
|
global wishlist_auto_timer
|
||||||
|
|
||||||
|
with wishlist_timer_lock:
|
||||||
|
# Stop any existing timer to prevent duplicates
|
||||||
|
if wishlist_auto_timer is not None:
|
||||||
|
wishlist_auto_timer.cancel()
|
||||||
|
|
||||||
|
print("🔄 Starting automatic wishlist processing system (1 minute initial delay)")
|
||||||
|
wishlist_auto_timer = threading.Timer(60.0, _process_wishlist_automatically) # 1 minute
|
||||||
|
wishlist_auto_timer.daemon = True
|
||||||
|
wishlist_auto_timer.start()
|
||||||
|
|
||||||
|
def stop_wishlist_auto_processing():
|
||||||
|
"""Stop automatic wishlist processing and cleanup timer."""
|
||||||
|
global wishlist_auto_timer, wishlist_auto_processing
|
||||||
|
|
||||||
|
with wishlist_timer_lock:
|
||||||
|
if wishlist_auto_timer is not None:
|
||||||
|
wishlist_auto_timer.cancel()
|
||||||
|
wishlist_auto_timer = None
|
||||||
|
print("⏹️ Stopped automatic wishlist processing")
|
||||||
|
|
||||||
|
wishlist_auto_processing = False
|
||||||
|
|
||||||
|
def schedule_next_wishlist_processing():
|
||||||
|
"""Schedule next automatic wishlist processing in 10 minutes."""
|
||||||
|
global wishlist_auto_timer
|
||||||
|
|
||||||
|
with wishlist_timer_lock:
|
||||||
|
print("⏰ Scheduling next automatic wishlist processing in 10 minutes")
|
||||||
|
wishlist_auto_timer = threading.Timer(600.0, _process_wishlist_automatically) # 10 minutes
|
||||||
|
wishlist_auto_timer.daemon = True
|
||||||
|
wishlist_auto_timer.start()
|
||||||
|
|
||||||
|
def _process_wishlist_automatically():
|
||||||
|
"""Main automatic processing logic that runs in background thread."""
|
||||||
|
global wishlist_auto_processing
|
||||||
|
|
||||||
|
print("🤖 Starting automatic wishlist processing...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with wishlist_timer_lock:
|
||||||
|
if wishlist_auto_processing:
|
||||||
|
print("⚠️ Wishlist auto-processing already running, skipping.")
|
||||||
|
schedule_next_wishlist_processing()
|
||||||
|
return
|
||||||
|
|
||||||
|
wishlist_auto_processing = True
|
||||||
|
|
||||||
|
# Use app context for database operations
|
||||||
|
with app.app_context():
|
||||||
|
from core.wishlist_service import get_wishlist_service
|
||||||
|
wishlist_service = get_wishlist_service()
|
||||||
|
|
||||||
|
# Check if wishlist has tracks
|
||||||
|
count = wishlist_service.get_wishlist_count()
|
||||||
|
if count == 0:
|
||||||
|
print("ℹ️ No tracks in wishlist for auto-processing.")
|
||||||
|
with wishlist_timer_lock:
|
||||||
|
wishlist_auto_processing = False
|
||||||
|
schedule_next_wishlist_processing()
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"🎵 Found {count} tracks in wishlist, starting automatic processing...")
|
||||||
|
|
||||||
|
# Check if wishlist processing is already active
|
||||||
|
playlist_id = "wishlist"
|
||||||
|
with tasks_lock:
|
||||||
|
for batch_id, batch_data in download_batches.items():
|
||||||
|
if (batch_data.get('playlist_id') == playlist_id and
|
||||||
|
batch_data.get('phase') not in ['complete', 'error', 'cancelled']):
|
||||||
|
print("⚠️ Wishlist processing already active in another batch, skipping automatic start")
|
||||||
|
with wishlist_timer_lock:
|
||||||
|
wishlist_auto_processing = False
|
||||||
|
schedule_next_wishlist_processing()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get wishlist tracks for processing
|
||||||
|
wishlist_tracks = wishlist_service.get_wishlist_tracks_for_download()
|
||||||
|
if not wishlist_tracks:
|
||||||
|
print("⚠️ No tracks returned from wishlist service.")
|
||||||
|
with wishlist_timer_lock:
|
||||||
|
wishlist_auto_processing = False
|
||||||
|
schedule_next_wishlist_processing()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create batch for automatic processing
|
||||||
|
batch_id = str(uuid.uuid4())
|
||||||
|
playlist_name = "Wishlist (Auto)"
|
||||||
|
|
||||||
|
# Create task queue - convert wishlist tracks to expected format
|
||||||
|
with tasks_lock:
|
||||||
|
download_batches[batch_id] = {
|
||||||
|
'phase': 'analysis',
|
||||||
|
'playlist_id': playlist_id,
|
||||||
|
'playlist_name': playlist_name,
|
||||||
|
'queue': [],
|
||||||
|
'active_count': 0,
|
||||||
|
'max_concurrent': 3,
|
||||||
|
'queue_index': 0,
|
||||||
|
'analysis_total': len(wishlist_tracks),
|
||||||
|
'analysis_processed': 0,
|
||||||
|
'analysis_results': [],
|
||||||
|
# Track state management (replicating sync.py)
|
||||||
|
'permanently_failed_tracks': [],
|
||||||
|
'cancelled_tracks': set(),
|
||||||
|
# Mark as auto-initiated
|
||||||
|
'auto_initiated': True,
|
||||||
|
'auto_processing_timestamp': time.time()
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"🚀 Starting automatic wishlist batch {batch_id} with {len(wishlist_tracks)} tracks")
|
||||||
|
|
||||||
|
# Submit the wishlist processing job using existing infrastructure
|
||||||
|
missing_download_executor.submit(_run_full_missing_tracks_process, batch_id, playlist_id, wishlist_tracks)
|
||||||
|
|
||||||
|
# Don't mark auto_processing as False here - let completion handler do it
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error in automatic wishlist processing: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
with wishlist_timer_lock:
|
||||||
|
wishlist_auto_processing = False
|
||||||
|
schedule_next_wishlist_processing()
|
||||||
|
|
||||||
# ===============================
|
# ===============================
|
||||||
# == DATABASE UPDATER API ==
|
# == DATABASE UPDATER API ==
|
||||||
# ===============================
|
# ===============================
|
||||||
|
|
@ -3879,6 +4019,49 @@ def _process_failed_tracks_to_wishlist_exact(batch_id):
|
||||||
|
|
||||||
return {'tracks_added': 0, 'errors': 1, 'total_failed': 0}
|
return {'tracks_added': 0, 'errors': 1, 'total_failed': 0}
|
||||||
|
|
||||||
|
def _process_failed_tracks_to_wishlist_exact_with_auto_completion(batch_id):
|
||||||
|
"""
|
||||||
|
Process failed tracks to wishlist for auto-initiated batches and handle auto-processing completion.
|
||||||
|
This extends the standard processing with automatic scheduling of the next cycle.
|
||||||
|
"""
|
||||||
|
global wishlist_auto_processing
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(f"🤖 [Auto-Wishlist] Processing completion for auto-initiated batch {batch_id}")
|
||||||
|
|
||||||
|
# Run standard wishlist processing
|
||||||
|
completion_summary = _process_failed_tracks_to_wishlist_exact(batch_id)
|
||||||
|
|
||||||
|
# Log auto-processing completion
|
||||||
|
tracks_added = completion_summary.get('tracks_added', 0)
|
||||||
|
total_failed = completion_summary.get('total_failed', 0)
|
||||||
|
print(f"🎉 [Auto-Wishlist] Background processing complete: {tracks_added} added to wishlist, {total_failed} failed")
|
||||||
|
|
||||||
|
# Mark auto-processing as complete
|
||||||
|
with wishlist_timer_lock:
|
||||||
|
wishlist_auto_processing = False
|
||||||
|
|
||||||
|
# Schedule next automatic processing cycle
|
||||||
|
print("⏰ [Auto-Wishlist] Scheduling next automatic cycle in 10 minutes")
|
||||||
|
schedule_next_wishlist_processing()
|
||||||
|
|
||||||
|
return completion_summary
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ [Auto-Wishlist] Error in auto-completion processing: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
# Ensure auto-processing flag is reset even on error
|
||||||
|
with wishlist_timer_lock:
|
||||||
|
wishlist_auto_processing = False
|
||||||
|
|
||||||
|
# Schedule next cycle even after error to maintain continuity
|
||||||
|
print("⏰ [Auto-Wishlist] Scheduling next cycle after error (10 minutes)")
|
||||||
|
schedule_next_wishlist_processing()
|
||||||
|
|
||||||
|
return {'tracks_added': 0, 'errors': 1, 'total_failed': 0}
|
||||||
|
|
||||||
def _on_download_completed(batch_id, task_id, success=True):
|
def _on_download_completed(batch_id, task_id, success=True):
|
||||||
"""Called when a download completes to start the next one in queue"""
|
"""Called when a download completes to start the next one in queue"""
|
||||||
with tasks_lock:
|
with tasks_lock:
|
||||||
|
|
@ -3930,6 +4113,9 @@ def _on_download_completed(batch_id, task_id, success=True):
|
||||||
if all_processed and no_active:
|
if all_processed and no_active:
|
||||||
print(f"🎉 [Batch Manager] Batch {batch_id} fully complete - processing failed tracks to wishlist")
|
print(f"🎉 [Batch Manager] Batch {batch_id} fully complete - processing failed tracks to wishlist")
|
||||||
|
|
||||||
|
# Check if this is an auto-initiated batch
|
||||||
|
is_auto_batch = batch.get('auto_initiated', False)
|
||||||
|
|
||||||
# Mark batch as complete and process wishlist outside of lock to prevent deadlocks
|
# Mark batch as complete and process wishlist outside of lock to prevent deadlocks
|
||||||
batch['phase'] = 'complete'
|
batch['phase'] = 'complete'
|
||||||
|
|
||||||
|
|
@ -3937,7 +4123,12 @@ def _on_download_completed(batch_id, task_id, success=True):
|
||||||
download_monitor.stop_monitoring(batch_id)
|
download_monitor.stop_monitoring(batch_id)
|
||||||
|
|
||||||
# Process wishlist outside of the lock to prevent threading issues
|
# Process wishlist outside of the lock to prevent threading issues
|
||||||
missing_download_executor.submit(_process_failed_tracks_to_wishlist_exact, batch_id)
|
if is_auto_batch:
|
||||||
|
# For auto-initiated batches, handle completion and schedule next cycle
|
||||||
|
missing_download_executor.submit(_process_failed_tracks_to_wishlist_exact_with_auto_completion, batch_id)
|
||||||
|
else:
|
||||||
|
# For manual batches, use standard wishlist processing
|
||||||
|
missing_download_executor.submit(_process_failed_tracks_to_wishlist_exact, batch_id)
|
||||||
return # Don't start next batch if we're done
|
return # Don't start next batch if we're done
|
||||||
|
|
||||||
# Start next downloads in queue
|
# Start next downloads in queue
|
||||||
|
|
@ -4495,7 +4686,8 @@ def get_batch_download_status(batch_id):
|
||||||
batch = download_batches[batch_id]
|
batch = download_batches[batch_id]
|
||||||
response_data = {
|
response_data = {
|
||||||
"phase": batch.get('phase', 'unknown'),
|
"phase": batch.get('phase', 'unknown'),
|
||||||
"error": batch.get('error')
|
"error": batch.get('error'),
|
||||||
|
"auto_initiated": batch.get('auto_initiated', False)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response_data["phase"] == 'analysis':
|
if response_data["phase"] == 'analysis':
|
||||||
|
|
@ -5370,4 +5562,9 @@ if __name__ == '__main__':
|
||||||
start_simple_background_monitor()
|
start_simple_background_monitor()
|
||||||
print("✅ Simple background monitor started")
|
print("✅ Simple background monitor started")
|
||||||
|
|
||||||
|
# Start automatic wishlist processing when server starts
|
||||||
|
print("🔧 Starting automatic wishlist processing...")
|
||||||
|
start_wishlist_auto_processing()
|
||||||
|
print("✅ Automatic wishlist processing started")
|
||||||
|
|
||||||
app.run(host='0.0.0.0', port=5001, debug=True)
|
app.run(host='0.0.0.0', port=5001, debug=True)
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,6 @@ let dbStatsInterval = null;
|
||||||
let dbUpdateStatusInterval = null;
|
let dbUpdateStatusInterval = null;
|
||||||
let wishlistCountInterval = null;
|
let wishlistCountInterval = null;
|
||||||
|
|
||||||
// --- Auto Wishlist Processing (Simple Timer System) ---
|
|
||||||
let autoWishlistTimer = null;
|
|
||||||
|
|
||||||
// --- Add these globals for the Sync Page ---
|
// --- Add these globals for the Sync Page ---
|
||||||
let spotifyPlaylists = [];
|
let spotifyPlaylists = [];
|
||||||
let selectedPlaylists = new Set();
|
let selectedPlaylists = new Set();
|
||||||
|
|
@ -278,7 +275,6 @@ async function loadPageData(pageId) {
|
||||||
stopDbStatsPolling();
|
stopDbStatsPolling();
|
||||||
stopDbUpdatePolling();
|
stopDbUpdatePolling();
|
||||||
stopWishlistCountPolling();
|
stopWishlistCountPolling();
|
||||||
stopAutoWishlistProcessing();
|
|
||||||
switch (pageId) {
|
switch (pageId) {
|
||||||
case 'dashboard':
|
case 'dashboard':
|
||||||
stopDownloadPolling();
|
stopDownloadPolling();
|
||||||
|
|
@ -1784,26 +1780,42 @@ async function checkForActiveProcesses() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function rehydrateModal(processInfo) {
|
async function rehydrateModal(processInfo, userRequested = false) {
|
||||||
const { playlist_id, playlist_name, batch_id } = processInfo;
|
const { playlist_id, playlist_name, batch_id } = processInfo;
|
||||||
console.log(`💧 Rehydrating modal for "${playlist_name}" (batch: ${batch_id})`);
|
console.log(`💧 Rehydrating modal for "${playlist_name}" (batch: ${batch_id}) - User requested: ${userRequested}`);
|
||||||
|
|
||||||
// Handle wishlist processes specially
|
// Handle wishlist processes specially
|
||||||
if (playlist_id === "wishlist") {
|
if (playlist_id === "wishlist") {
|
||||||
|
console.log(`🔍 Current activeDownloadProcesses keys: [${Object.keys(activeDownloadProcesses).join(', ')}]`);
|
||||||
|
|
||||||
await openDownloadMissingWishlistModal();
|
await openDownloadMissingWishlistModal();
|
||||||
const process = activeDownloadProcesses[playlist_id];
|
const process = activeDownloadProcesses[playlist_id];
|
||||||
if (!process) return;
|
if (!process) {
|
||||||
|
console.error('❌ Failed to create wishlist process in activeDownloadProcesses');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✅ Setting wishlist process state - batchId: ${batch_id}, status: running`);
|
||||||
process.status = 'running';
|
process.status = 'running';
|
||||||
process.batchId = batch_id;
|
process.batchId = batch_id;
|
||||||
updateRefreshButtonState();
|
updateRefreshButtonState();
|
||||||
|
|
||||||
document.getElementById(`begin-analysis-btn-${playlist_id}`).style.display = 'none';
|
const beginBtn = document.getElementById(`begin-analysis-btn-${playlist_id}`);
|
||||||
document.getElementById(`cancel-all-btn-${playlist_id}`).style.display = 'inline-block';
|
const cancelBtn = document.getElementById(`cancel-all-btn-${playlist_id}`);
|
||||||
|
|
||||||
|
if (beginBtn) beginBtn.style.display = 'none';
|
||||||
|
if (cancelBtn) cancelBtn.style.display = 'inline-block';
|
||||||
|
|
||||||
startModalDownloadPolling(playlist_id);
|
startModalDownloadPolling(playlist_id);
|
||||||
|
|
||||||
process.modalElement.style.display = 'none';
|
// Only hide modal if this is background rehydration, not user-requested
|
||||||
|
if (!userRequested) {
|
||||||
|
process.modalElement.style.display = 'none';
|
||||||
|
console.log('🔍 Hiding wishlist modal for background rehydration');
|
||||||
|
} else {
|
||||||
|
process.modalElement.style.display = 'flex';
|
||||||
|
console.log('🔍 Showing wishlist modal as requested by user');
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2668,8 +2680,8 @@ function startModalDownloadPolling(playlistId) {
|
||||||
// Enhanced check for background auto-processing for wishlist
|
// Enhanced check for background auto-processing for wishlist
|
||||||
const isWishlist = (playlistId === 'wishlist');
|
const isWishlist = (playlistId === 'wishlist');
|
||||||
const isModalHidden = (process.modalElement && process.modalElement.style.display === 'none');
|
const isModalHidden = (process.modalElement && process.modalElement.style.display === 'none');
|
||||||
const hasAutoTimer = (autoWishlistTimer !== null);
|
const isAutoInitiated = data.auto_initiated || false; // Server indicates if batch was auto-started
|
||||||
const isBackgroundWishlist = isWishlist && (isModalHidden || hasAutoTimer);
|
const isBackgroundWishlist = isWishlist && (isModalHidden || isAutoInitiated);
|
||||||
|
|
||||||
if (data.phase === 'cancelled') {
|
if (data.phase === 'cancelled') {
|
||||||
process.status = 'cancelled';
|
process.status = 'cancelled';
|
||||||
|
|
@ -2690,7 +2702,7 @@ function startModalDownloadPolling(playlistId) {
|
||||||
// Reset modal to idle state to prevent "complete" phase disruption
|
// Reset modal to idle state to prevent "complete" phase disruption
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resetWishlistModalToIdleState();
|
resetWishlistModalToIdleState();
|
||||||
scheduleNextAutoWishlist(); // Schedule next cycle
|
// Server-side auto-processing will handle next cycle automatically
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
return; // Skip normal completion handling
|
return; // Skip normal completion handling
|
||||||
|
|
@ -5274,124 +5286,7 @@ function stopWishlistCountPolling() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Auto Wishlist Processing (Simple Implementation) ---
|
|
||||||
|
|
||||||
function startAutoWishlistProcessing() {
|
|
||||||
console.log("🔄 Starting automatic wishlist processing system (1 minute initial delay)");
|
|
||||||
stopAutoWishlistProcessing(); // Prevent duplicates
|
|
||||||
autoWishlistTimer = setTimeout(processWishlistAutomatically, 60000); // 1 minute
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopAutoWishlistProcessing() {
|
|
||||||
if (autoWishlistTimer) {
|
|
||||||
clearTimeout(autoWishlistTimer);
|
|
||||||
autoWishlistTimer = null;
|
|
||||||
console.log("⏹️ Stopped automatic wishlist processing");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function scheduleNextAutoWishlist() {
|
|
||||||
console.log("⏰ Scheduling next automatic wishlist processing in 10 minutes");
|
|
||||||
autoWishlistTimer = setTimeout(processWishlistAutomatically, 600000); // 10 minutes
|
|
||||||
}
|
|
||||||
|
|
||||||
async function processWishlistAutomatically() {
|
|
||||||
console.log("🤖 Starting automatic wishlist processing...");
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Check if wishlist has tracks
|
|
||||||
const countResponse = await fetch('/api/wishlist/count');
|
|
||||||
if (!countResponse.ok) {
|
|
||||||
console.warn("⚠️ Could not fetch wishlist count, will retry next cycle");
|
|
||||||
scheduleNextAutoWishlist();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const countData = await countResponse.json();
|
|
||||||
if (countData.count === 0) {
|
|
||||||
console.log("ℹ️ Wishlist is empty, skipping automatic processing");
|
|
||||||
scheduleNextAutoWishlist();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`🎵 Found ${countData.count} tracks in wishlist, starting automatic processing...`);
|
|
||||||
|
|
||||||
const playlistId = 'wishlist';
|
|
||||||
|
|
||||||
// Check if processing is already active
|
|
||||||
if (activeDownloadProcesses[playlistId] && activeDownloadProcesses[playlistId].status === 'running') {
|
|
||||||
console.log("⚠️ Wishlist processing already active, skipping automatic start");
|
|
||||||
scheduleNextAutoWishlist();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if modal is currently being viewed by user
|
|
||||||
const existingModal = document.getElementById(`download-missing-modal-${playlistId}`);
|
|
||||||
const userIsViewingModal = existingModal && existingModal.style.display === 'flex';
|
|
||||||
|
|
||||||
console.log("🤖 Setting up wishlist modal for automatic processing");
|
|
||||||
console.log(`🔍 User currently viewing modal: ${userIsViewingModal}`);
|
|
||||||
|
|
||||||
// Create modal if it doesn't exist, but keep it hidden for background processing
|
|
||||||
if (!existingModal) {
|
|
||||||
await openDownloadMissingWishlistModal();
|
|
||||||
// Immediately hide the modal since this is background processing
|
|
||||||
const modal = document.getElementById(`download-missing-modal-${playlistId}`);
|
|
||||||
if (modal) {
|
|
||||||
modal.style.display = 'none';
|
|
||||||
console.log("🤖 Modal created and hidden for background processing");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait a moment for modal to be ready, then programmatically click "Begin Analysis"
|
|
||||||
setTimeout(() => {
|
|
||||||
const beginButton = document.getElementById(`begin-analysis-btn-${playlistId}`);
|
|
||||||
const modal = document.getElementById(`download-missing-modal-${playlistId}`);
|
|
||||||
|
|
||||||
console.log(`🔍 Looking for button with ID: begin-analysis-btn-${playlistId}`);
|
|
||||||
console.log(`🔍 Button found:`, beginButton);
|
|
||||||
|
|
||||||
if (beginButton) {
|
|
||||||
// Check if button is visible and clickable
|
|
||||||
const buttonStyle = window.getComputedStyle(beginButton);
|
|
||||||
console.log(`🔍 Button display style:`, buttonStyle.display);
|
|
||||||
console.log(`🔍 Button disabled:`, beginButton.disabled);
|
|
||||||
|
|
||||||
if (buttonStyle.display === 'none' || beginButton.disabled) {
|
|
||||||
console.warn("⚠️ Begin Analysis button is hidden or disabled, skipping automatic click");
|
|
||||||
scheduleNextAutoWishlist();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("🤖 Programmatically clicking 'Begin Analysis' button");
|
|
||||||
beginButton.click();
|
|
||||||
|
|
||||||
// Only hide modal if user wasn't actively viewing it
|
|
||||||
if (!userIsViewingModal) {
|
|
||||||
setTimeout(() => {
|
|
||||||
const modal = document.getElementById(`download-missing-modal-${playlistId}`);
|
|
||||||
if (modal && modal.style.display !== 'none') {
|
|
||||||
modal.style.display = 'none';
|
|
||||||
console.log("🤖 Modal hidden - processing continues in background");
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
} else {
|
|
||||||
console.log("🤖 User is viewing modal - keeping it visible with live updates");
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
console.warn("⚠️ Could not find Begin Analysis button, will retry next cycle");
|
|
||||||
console.log("🔍 Available buttons with 'begin-analysis' in ID:",
|
|
||||||
Array.from(document.querySelectorAll('[id*="begin-analysis"]')).map(el => el.id));
|
|
||||||
scheduleNextAutoWishlist();
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ Error in automatic wishlist processing:", error);
|
|
||||||
scheduleNextAutoWishlist();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetWishlistModalToIdleState() {
|
function resetWishlistModalToIdleState() {
|
||||||
// Reset wishlist modal to idle state after background processing completes
|
// Reset wishlist modal to idle state after background processing completes
|
||||||
|
|
@ -5491,8 +5386,7 @@ async function loadDashboardData() {
|
||||||
// Check for any active download processes that need rehydration
|
// Check for any active download processes that need rehydration
|
||||||
await checkForActiveProcesses();
|
await checkForActiveProcesses();
|
||||||
|
|
||||||
// Start automatic wishlist processing (1 minute delay, then every 10 minutes)
|
// Automatic wishlist processing now runs server-side
|
||||||
startAutoWishlistProcessing();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Data Fetching and UI Updates ---
|
// --- Data Fetching and UI Updates ---
|
||||||
|
|
@ -5736,16 +5630,59 @@ async function handleDbUpdateButtonClick() {
|
||||||
|
|
||||||
async function handleWishlistButtonClick() {
|
async function handleWishlistButtonClick() {
|
||||||
try {
|
try {
|
||||||
// First check if there are any tracks in the wishlist
|
const playlistId = 'wishlist';
|
||||||
const response = await fetch('/api/wishlist/count');
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.count === 0) {
|
// Always check server state first to detect any active wishlist processes
|
||||||
|
const response = await fetch('/api/active-processes');
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
const processes = data.active_processes || [];
|
||||||
|
|
||||||
|
// Look for active wishlist process on server
|
||||||
|
const serverWishlistProcess = processes.find(p => p.playlist_id === playlistId);
|
||||||
|
const clientWishlistProcess = activeDownloadProcesses[playlistId];
|
||||||
|
|
||||||
|
if (serverWishlistProcess) {
|
||||||
|
console.log('Server has active wishlist process:', serverWishlistProcess.batch_id);
|
||||||
|
|
||||||
|
// Check if client state is properly synchronized with server
|
||||||
|
const isClientStateMismatched = !clientWishlistProcess ||
|
||||||
|
clientWishlistProcess.batchId !== serverWishlistProcess.batch_id ||
|
||||||
|
clientWishlistProcess.status !== 'running';
|
||||||
|
|
||||||
|
const isModalElementMissing = !clientWishlistProcess?.modalElement ||
|
||||||
|
!document.body.contains(clientWishlistProcess.modalElement);
|
||||||
|
|
||||||
|
if (isClientStateMismatched || isModalElementMissing) {
|
||||||
|
console.log('Server/client wishlist state mismatch or missing modal, rehydrating...');
|
||||||
|
console.log('Client batch ID:', clientWishlistProcess?.batchId, 'Server batch ID:', serverWishlistProcess.batch_id);
|
||||||
|
console.log('Client status:', clientWishlistProcess?.status, 'Modal exists:', !isModalElementMissing);
|
||||||
|
|
||||||
|
await rehydrateModal(serverWishlistProcess, true); // Force rehydration with user request
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Client state is properly synchronized, just show existing modal
|
||||||
|
console.log('Found properly synced wishlist process, showing modal');
|
||||||
|
if (clientWishlistProcess.modalElement) {
|
||||||
|
clientWishlistProcess.modalElement.style.display = 'flex';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No active wishlist process on server, proceed with normal modal creation
|
||||||
|
// First check if there are any tracks in the wishlist
|
||||||
|
const countResponse = await fetch('/api/wishlist/count');
|
||||||
|
const countData = await countResponse.json();
|
||||||
|
|
||||||
|
if (countData.count === 0) {
|
||||||
showToast('Wishlist is empty. No tracks to download.', 'info');
|
showToast('Wishlist is empty. No tracks to download.', 'info');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the wishlist download modal
|
// Open the wishlist download modal for new process
|
||||||
|
console.log('No active server process, creating new wishlist modal');
|
||||||
await openDownloadMissingWishlistModal();
|
await openDownloadMissingWishlistModal();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -5812,7 +5749,4 @@ async function clearWishlist(playlistId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Global Cleanup on Page Unload ---
|
// --- Global Cleanup on Page Unload ---
|
||||||
window.addEventListener('beforeunload', function() {
|
// Note: Automatic wishlist processing now runs server-side and continues even when browser is closed
|
||||||
console.log("🧹 Page unload - stopping auto wishlist processing");
|
|
||||||
stopAutoWishlistProcessing();
|
|
||||||
});
|
|
||||||
Loading…
Reference in a new issue