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,
This commit is contained in:
parent
73679f5709
commit
2ad0dddba4
9 changed files with 157 additions and 12 deletions
|
|
@ -4,7 +4,8 @@
|
|||
"Bash(mkdir:*)",
|
||||
"Bash(rm:*)",
|
||||
"Bash(rg:*)",
|
||||
"Bash(grep:*)"
|
||||
"Bash(grep:*)",
|
||||
"WebFetch(domain:python-plexapi.readthedocs.io)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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
|
||||
})
|
||||
Binary file not shown.
|
|
@ -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
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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;")
|
||||
Binary file not shown.
|
|
@ -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…
Reference in a new issue