From 2ad0dddba44a7133ba103ceb9ee7a778bdc0b878 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Thu, 7 Aug 2025 20:27:26 -0700 Subject: [PATCH] recommend database refresh every 1-2weeks This is needed if users manually correct failed matches on plex when songs are scanned in. If a db sync is done after the scan but before the manual modification, --- .claude/settings.local.json | 3 +- .../database_update_worker.cpython-312.pyc | Bin 24687 -> 25251 bytes core/database_update_worker.py | 18 ++++- .../music_database.cpython-312.pyc | Bin 59143 -> 61848 bytes database/music_database.py | 52 ++++++++++++- .../database_updater_widget.cpython-312.pyc | Bin 16539 -> 20320 bytes ui/components/database_updater_widget.py | 71 +++++++++++++++++- .../__pycache__/dashboard.cpython-312.pyc | Bin 96185 -> 96983 bytes ui/pages/dashboard.py | 25 +++++- 9 files changed, 157 insertions(+), 12 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index e3e0fee4..d76aadf0 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -4,7 +4,8 @@ "Bash(mkdir:*)", "Bash(rm:*)", "Bash(rg:*)", - "Bash(grep:*)" + "Bash(grep:*)", + "WebFetch(domain:python-plexapi.readthedocs.io)" ], "deny": [] } diff --git a/core/__pycache__/database_update_worker.cpython-312.pyc b/core/__pycache__/database_update_worker.cpython-312.pyc index 58e50de35c66c85bf2807c5dea3d17d003a7ac37..0bf4f6573ac1f20b1f506c58546f034e7bacddfa 100644 GIT binary patch delta 1997 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 OnZ$ni*>w`e#{UBE^5BmE delta 7550 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 oGdmNJ?wHodkI*Rnx9H+$XZPep^O^6Q1$cH Optional[str]: + """Get a metadata value""" + try: + with self._get_connection() as conn: + cursor = conn.cursor() + cursor.execute("SELECT value FROM metadata WHERE key = ?", (key,)) + result = cursor.fetchone() + return result['value'] if result else None + except Exception as e: + logger.error(f"Error getting metadata {key}: {e}") + return None + + def record_full_refresh_completion(self): + """Record when a full refresh was completed""" + from datetime import datetime + self.set_metadata('last_full_refresh', datetime.now().isoformat()) + + def get_last_full_refresh(self) -> Optional[str]: + """Get the date of the last full refresh""" + return self.get_metadata('last_full_refresh') + def get_database_info(self) -> Dict[str, Any]: """Get comprehensive database information""" try: @@ -1357,11 +1400,15 @@ class MusicDatabase: result = cursor.fetchone() last_update = result['last_update'] if result and result['last_update'] else None + # Get last full refresh + last_full_refresh = self.get_last_full_refresh() + return { **stats, 'database_size_mb': round(db_size_mb, 2), 'database_path': str(self.database_path), - 'last_update': last_update + 'last_update': last_update, + 'last_full_refresh': last_full_refresh } except Exception as e: @@ -1372,7 +1419,8 @@ class MusicDatabase: 'tracks': 0, 'database_size_mb': 0.0, 'database_path': str(self.database_path), - 'last_update': None + 'last_update': None, + 'last_full_refresh': None } # Thread-safe singleton pattern for database access diff --git a/ui/components/__pycache__/database_updater_widget.cpython-312.pyc b/ui/components/__pycache__/database_updater_widget.cpython-312.pyc index 11560c50f2ca8a232545ba4e32163251e9e7dd86..1e3723a33ec4c99624e299f40d99dd37d477f83e 100644 GIT binary patch delta 5562 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 AX8-^I diff --git a/ui/components/database_updater_widget.py b/ui/components/database_updater_widget.py index ab2238be..59eff0be 100644 --- a/ui/components/database_updater_widget.py +++ b/ui/components/database_updater_widget.py @@ -4,6 +4,9 @@ from PyQt6.QtWidgets import (QFrame, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QProgressBar, QComboBox, QGroupBox) from PyQt6.QtCore import Qt from PyQt6.QtGui import QFont +from utils.logging_config import get_logger + +logger = get_logger("database_updater_widget") class DatabaseUpdaterWidget(QFrame): """UI widget for updating SoulSync database with Plex library data""" @@ -36,6 +39,18 @@ class DatabaseUpdaterWidget(QFrame): info_label.setStyleSheet("color: #b3b3b3; margin-bottom: 5px;") info_label.setWordWrap(True) + # Recommendation label + self.recommendation_label = QLabel("💡 Tip: Run a Full Refresh every 1-2 weeks to ensure database accuracy") + self.recommendation_label.setFont(QFont("Arial", 9)) + self.recommendation_label.setStyleSheet("color: #ffaa00; margin-bottom: 8px; padding: 6px 8px; background: #332200; border-radius: 4px;") + self.recommendation_label.setWordWrap(True) + + # Last full refresh label + self.last_refresh_label = QLabel("") + self.last_refresh_label.setFont(QFont("Arial", 8)) + self.last_refresh_label.setStyleSheet("color: #888888; margin-bottom: 5px;") + self.last_refresh_label.setWordWrap(True) + # Control section control_layout = QVBoxLayout() control_layout.setSpacing(12) @@ -270,6 +285,8 @@ class DatabaseUpdaterWidget(QFrame): # Add all sections to main layout layout.addWidget(header_label) layout.addWidget(info_label) + layout.addWidget(self.recommendation_label) + layout.addWidget(self.last_refresh_label) layout.addLayout(control_layout) layout.addLayout(progress_layout) layout.addWidget(stats_group) @@ -312,4 +329,56 @@ class DatabaseUpdaterWidget(QFrame): def set_button_enabled(self, enabled: bool): """Enable/disable the start button""" - self.start_button.setEnabled(enabled) \ No newline at end of file + self.start_button.setEnabled(enabled) + + def update_last_refresh_info(self, last_refresh_date: str = None): + """Update the last refresh information with color-coded warnings""" + if not last_refresh_date: + self.last_refresh_label.setText("No full refresh recorded") + self.last_refresh_label.setStyleSheet("color: #ff6666; margin-bottom: 5px; font-style: italic;") + self._update_recommendation_urgency(urgent=True) + return + + try: + from datetime import datetime + last_date = datetime.fromisoformat(last_refresh_date.replace('Z', '+00:00')) + days_ago = (datetime.now() - last_date.replace(tzinfo=None)).days + + if days_ago == 0: + time_text = "today" + color = "#1db954" # Green + urgent = False + elif days_ago == 1: + time_text = "yesterday" + color = "#1db954" # Green + urgent = False + elif days_ago < 7: + time_text = f"{days_ago} days ago" + color = "#1db954" # Green + urgent = False + elif days_ago < 14: + time_text = f"{days_ago} days ago" + color = "#ffaa00" # Orange warning + urgent = False + else: + time_text = f"{days_ago} days ago" + color = "#ff6666" # Red warning + urgent = True + + self.last_refresh_label.setText(f"Last full refresh: {time_text}") + self.last_refresh_label.setStyleSheet(f"color: {color}; margin-bottom: 5px;") + self._update_recommendation_urgency(urgent=urgent) + + except Exception: + self.last_refresh_label.setText("Last full refresh: unknown") + self.last_refresh_label.setStyleSheet("color: #888888; margin-bottom: 5px;") + self._update_recommendation_urgency(urgent=False) + + def _update_recommendation_urgency(self, urgent: bool = False): + """Update the recommendation label styling based on urgency""" + if urgent: + self.recommendation_label.setText("⚠️ Recommended: Run a Full Refresh - it's been over 2 weeks!") + self.recommendation_label.setStyleSheet("color: #ffffff; margin-bottom: 8px; padding: 6px 8px; background: #cc3300; border-radius: 4px;") + else: + self.recommendation_label.setText("💡 Tip: Run a Full Refresh every 1-2 weeks to ensure database accuracy") + self.recommendation_label.setStyleSheet("color: #ffaa00; margin-bottom: 8px; padding: 6px 8px; background: #332200; border-radius: 4px;") \ No newline at end of file diff --git a/ui/pages/__pycache__/dashboard.cpython-312.pyc b/ui/pages/__pycache__/dashboard.cpython-312.pyc index 88b705f020980d2e4ea0e5d4b62c2de79389ddf8..7c5157974de92e569917655c8d8b433239f4a1f5 100644 GIT binary patch delta 14943 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