diff --git a/web_server.py b/web_server.py index 1348f14d..a1415f82 100644 --- a/web_server.py +++ b/web_server.py @@ -24,6 +24,8 @@ from core.jellyfin_client import JellyfinClient from core.soulseek_client import SoulseekClient from core.tidal_client import TidalClient # Added import for Tidal from core.matching_engine import MusicMatchingEngine +from core.database_update_worker import DatabaseUpdateWorker, DatabaseStatsWorker +from database.music_database import get_database # --- Flask App Setup --- base_dir = os.path.abspath(os.path.dirname(__file__)) @@ -81,6 +83,19 @@ stream_lock = threading.Lock() # Prevent race conditions stream_background_task = None stream_executor = ThreadPoolExecutor(max_workers=1) # Only one stream at a time +db_update_executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix="DBUpdate") +db_update_worker = None +db_update_state = { + "status": "idle", # idle, running, finished, error + "phase": "Idle", + "progress": 0, + "current_item": "", + "processed": 0, + "total": 0, + "error_message": "" +} +db_update_lock = threading.Lock() + # --- Global Matched Downloads Context Management --- # Thread-safe storage for matched download contexts # Key: slskd download ID, Value: dict containing Spotify artist/album data @@ -2278,6 +2293,120 @@ def start_simple_background_monitor(): import traceback traceback.print_exc() time.sleep(10) + + +# =============================== +# == DATABASE UPDATER API == +# =============================== + +def _db_update_progress_callback(current_item, processed, total, percentage): + with db_update_lock: + db_update_state.update({ + "current_item": current_item, + "processed": processed, + "total": total, + "progress": percentage + }) + +def _db_update_phase_callback(phase): + with db_update_lock: + db_update_state["phase"] = phase + +def _db_update_finished_callback(total_artists, total_albums, total_tracks, successful, failed): + with db_update_lock: + db_update_state["status"] = "finished" + db_update_state["phase"] = f"Completed: {successful} successful, {failed} failed." + +def _db_update_error_callback(error_message): + with db_update_lock: + db_update_state["status"] = "error" + db_update_state["error_message"] = error_message + +def _run_db_update_task(full_refresh, server_type): + """The actual function that runs in the background thread.""" + global db_update_worker + media_client = None + + if server_type == "plex": + media_client = plex_client + elif server_type == "jellyfin": + media_client = jellyfin_client + + if not media_client: + _db_update_error_callback(f"Media client for '{server_type}' not available.") + return + + with db_update_lock: + db_update_worker = DatabaseUpdateWorker( + media_client=media_client, + full_refresh=full_refresh, + server_type=server_type + ) + # Connect signals to callbacks + db_update_worker.progress_updated.connect(_db_update_progress_callback) + db_update_worker.phase_changed.connect(_db_update_phase_callback) + db_update_worker.finished.connect(_db_update_finished_callback) + db_update_worker.error.connect(_db_update_error_callback) + + # This is a blocking call that runs the QThread's logic + db_update_worker.run() + + +@app.route('/api/database/stats', methods=['GET']) +def get_database_stats(): + """Endpoint to get current database statistics.""" + try: + # This logic is adapted from DatabaseStatsWorker + db = get_database() + stats = db.get_database_info_for_server() + return jsonify(stats) + except Exception as e: + print(f"Error getting database stats: {e}") + return jsonify({"error": str(e)}), 500 + +@app.route('/api/database/update', methods=['POST']) +def start_database_update(): + """Endpoint to start the database update process.""" + global db_update_worker + with db_update_lock: + if db_update_state["status"] == "running": + return jsonify({"success": False, "error": "An update is already in progress."}), 409 + + data = request.get_json() + full_refresh = data.get('full_refresh', False) + active_server = config_manager.get_active_media_server() + + db_update_state.update({ + "status": "running", + "phase": "Initializing...", + "progress": 0, "current_item": "", "processed": 0, "total": 0, "error_message": "" + }) + + # Submit the worker function to the executor + db_update_executor.submit(_run_db_update_task, full_refresh, active_server) + + return jsonify({"success": True, "message": "Database update started."}) + +@app.route('/api/database/update/status', methods=['GET']) +def get_database_update_status(): + """Endpoint to poll for the current update status.""" + with db_update_lock: + return jsonify(db_update_state) + +@app.route('/api/database/update/stop', methods=['POST']) +def stop_database_update(): + """Endpoint to stop the current database update.""" + global db_update_worker + with db_update_lock: + if db_update_worker and db_update_state["status"] == "running": + db_update_worker.stop() + db_update_state["status"] = "finished" + db_update_state["phase"] = "Update stopped by user." + return jsonify({"success": True, "message": "Stop request sent."}) + else: + return jsonify({"success": False, "error": "No update is currently running."}), 404 + + monitor_thread = threading.Thread(target=simple_monitor) monitor_thread.daemon = True diff --git a/webui/index.html b/webui/index.html index d64a6214..656584b7 100644 --- a/webui/index.html +++ b/webui/index.html @@ -222,6 +222,24 @@

Database Updater

Last Full Refresh: Never

+
+
+ Artists: + 0 +
+
+ Albums: + 0 +
+
+ Tracks: + 0 +
+
+ Size: + 0.0 MB +
+