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,
pull/2/head
Broque Thomas 9 months ago
parent 73679f5709
commit 2ad0dddba4

@ -4,7 +4,8 @@
"Bash(mkdir:*)",
"Bash(rm:*)",
"Bash(rg:*)",
"Bash(grep:*)"
"Bash(grep:*)",
"WebFetch(domain:python-plexapi.readthedocs.io)"
],
"deny": []
}

@ -80,6 +80,14 @@ class DatabaseUpdateWorker(QThread):
self.phase_changed.emit("Processing artists, albums, and tracks...")
self._process_all_artists(artists_to_process)
# Record full refresh completion for tracking purposes
if self.full_refresh and self.database:
try:
self.database.record_full_refresh_completion()
logger.info("Full refresh completion recorded in database")
except Exception as e:
logger.warning(f"Could not record full refresh completion: {e}")
# Emit final results
self.finished.emit(
self.processed_artists,
@ -483,7 +491,7 @@ class DatabaseStatsWorker(QThread):
self.should_stop = True
def run(self):
"""Get database statistics"""
"""Get database statistics and full info including last refresh"""
try:
if self.should_stop:
return
@ -492,9 +500,10 @@ class DatabaseStatsWorker(QThread):
if self.should_stop:
return
stats = database.get_database_info()
# Get full database info (includes last_full_refresh)
info = database.get_database_info()
if not self.should_stop:
self.stats_updated.emit(stats)
self.stats_updated.emit(info)
except Exception as e:
logger.error(f"Error getting database stats: {e}")
if not self.should_stop:
@ -503,5 +512,6 @@ class DatabaseStatsWorker(QThread):
'albums': 0,
'tracks': 0,
'database_size_mb': 0.0,
'last_update': None
'last_update': None,
'last_full_refresh': None
})

@ -126,6 +126,15 @@ class MusicDatabase:
)
""")
# Metadata table for storing system information like last refresh dates
cursor.execute("""
CREATE TABLE IF NOT EXISTS metadata (
key TEXT PRIMARY KEY,
value TEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
# Create indexes for performance
cursor.execute("CREATE INDEX IF NOT EXISTS idx_albums_artist_id ON albums (artist_id)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_tracks_album_id ON tracks (album_id)")
@ -1330,6 +1339,40 @@ class MusicDatabase:
logger.error(f"Error getting album completion stats for artist '{artist_name}': {e}")
return {'complete': 0, 'nearly_complete': 0, 'partial': 0, 'missing': 0, 'total': 0}
def set_metadata(self, key: str, value: str):
"""Set a metadata value"""
try:
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT OR REPLACE INTO metadata (key, value, updated_at)
VALUES (?, ?, CURRENT_TIMESTAMP)
""", (key, value))
conn.commit()
except Exception as e:
logger.error(f"Error setting metadata {key}: {e}")
def get_metadata(self, key: str) -> 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

@ -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)
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;")

@ -22,6 +22,9 @@ import io
from core.matching_engine import MusicMatchingEngine
from ui.components.database_updater_widget import DatabaseUpdaterWidget
from core.database_update_worker import DatabaseUpdateWorker, DatabaseStatsWorker
from utils.logging_config import get_logger
logger = get_logger("dashboard")
class MetadataUpdateWorker(QThread):
"""Worker thread for updating Plex artist metadata using Spotify data"""
@ -1588,7 +1591,7 @@ class DashboardPage(QWidget):
self._active_stats_workers.append(stats_worker)
# Connect signals
stats_worker.stats_updated.connect(self.database_widget.update_statistics)
stats_worker.stats_updated.connect(self.update_database_info)
stats_worker.finished.connect(lambda: self._cleanup_stats_worker(stats_worker))
stats_worker.start()
@ -1596,12 +1599,26 @@ class DashboardPage(QWidget):
logger.error(f"Error refreshing database statistics: {e}")
# Fallback to default stats to prevent crashes
if hasattr(self, 'database_widget') and self.database_widget:
self.database_widget.update_statistics({
fallback_info = {
'artists': 0,
'albums': 0,
'tracks': 0,
'database_size_mb': 0.0
})
'database_size_mb': 0.0,
'last_full_refresh': None
}
self.update_database_info(fallback_info)
def update_database_info(self, info: dict):
"""Update database statistics and last refresh info"""
try:
# Update basic statistics
self.database_widget.update_statistics(info)
# Update last refresh information
last_refresh_date = info.get('last_full_refresh')
self.database_widget.update_last_refresh_info(last_refresh_date)
except Exception as e:
logger.error(f"Error updating database info: {e}")
def _cleanup_stats_worker(self, worker):
"""Clean up a finished stats worker"""

Loading…
Cancel
Save