You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
SoulSync/ui/components/database_updater_widget.py

391 lines
16 KiB

#!/usr/bin/env python3
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"""
def __init__(self, parent=None):
super().__init__(parent)
self.setup_ui()
def setup_ui(self):
self.setStyleSheet("""
DatabaseUpdaterWidget {
background: #282828;
border-radius: 8px;
border: 1px solid #404040;
}
""")
layout = QVBoxLayout(self)
layout.setContentsMargins(20, 15, 20, 15)
layout.setSpacing(12)
# Header
header_label = QLabel("Update SoulSync Database")
header_label.setFont(QFont("Arial", 14, QFont.Weight.Bold))
header_label.setStyleSheet("color: #ffffff;")
# Info label - dynamic based on active server
try:
from config.settings import config_manager
active_server = config_manager.get_active_media_server()
server_name = "Jellyfin" if active_server == "jellyfin" else "Plex"
except:
server_name = "Plex" # Fallback
info_label = QLabel(f"Syncs your {server_name} music library into the local database for faster searches and analytics")
info_label.setFont(QFont("Arial", 9))
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)
# Top row: Button
button_layout = QHBoxLayout()
self.start_button = QPushButton("Update Database")
self.start_button.setFixedHeight(36)
self.start_button.setFont(QFont("Arial", 10, QFont.Weight.Medium))
self.start_button.setStyleSheet("""
QPushButton {
background: #1db954;
color: white;
border: none;
border-radius: 6px;
padding: 8px 16px;
}
QPushButton:hover {
background: #1ed760;
}
QPushButton:pressed {
background: #169c46;
}
QPushButton:disabled {
background: #555555;
color: #999999;
}
""")
button_layout.addWidget(self.start_button)
button_layout.addStretch()
# Bottom row: Settings and status
settings_layout = QHBoxLayout()
settings_layout.setSpacing(25)
# Update type dropdown
update_type_layout = QVBoxLayout()
update_type_layout.setSpacing(4)
type_label = QLabel("Update Type:")
type_label.setFont(QFont("Arial", 9))
type_label.setStyleSheet("color: #b3b3b3;")
self.update_type_combo = QComboBox()
self.update_type_combo.setFixedHeight(32)
self.update_type_combo.setFont(QFont("Arial", 10))
self.update_type_combo.addItems([
"Incremental Update",
"Full Refresh"
])
self.update_type_combo.setCurrentText("Incremental Update")
self.update_type_combo.setStyleSheet("""
QComboBox {
background: #333333;
color: #ffffff;
border: 1px solid #555555;
border-radius: 4px;
padding: 4px 8px;
min-width: 140px;
}
QComboBox:hover {
border: 1px solid #1db954;
}
QComboBox::drop-down {
border: none;
width: 20px;
}
QComboBox::down-arrow {
image: none;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid #ffffff;
margin-right: 5px;
}
QComboBox QAbstractItemView {
background: #333333;
color: #ffffff;
border: 1px solid #555555;
selection-background-color: #1db954;
}
""")
update_type_layout.addWidget(type_label)
update_type_layout.addWidget(self.update_type_combo)
# Current status display
status_layout = QVBoxLayout()
status_layout.setSpacing(4)
current_label = QLabel("Current Status:")
current_label.setFont(QFont("Arial", 9))
current_label.setStyleSheet("color: #b3b3b3;")
self.current_status_label = QLabel("Ready")
self.current_status_label.setFont(QFont("Arial", 11, QFont.Weight.Medium))
self.current_status_label.setStyleSheet("color: #ffffff;")
status_layout.addWidget(current_label)
status_layout.addWidget(self.current_status_label)
settings_layout.addLayout(update_type_layout)
settings_layout.addLayout(status_layout)
settings_layout.addStretch()
control_layout.addLayout(button_layout)
control_layout.addLayout(settings_layout)
# Progress section
progress_layout = QVBoxLayout()
progress_layout.setSpacing(8)
progress_info_layout = QHBoxLayout()
self.progress_label = QLabel("Progress: 0%")
self.progress_label.setFont(QFont("Arial", 10))
self.progress_label.setStyleSheet("color: #ffffff;")
self.count_label = QLabel("0 artists processed")
self.count_label.setFont(QFont("Arial", 9))
self.count_label.setStyleSheet("color: #b3b3b3;")
progress_info_layout.addWidget(self.progress_label)
progress_info_layout.addStretch()
progress_info_layout.addWidget(self.count_label)
self.progress_bar = QProgressBar()
self.progress_bar.setFixedHeight(8)
self.progress_bar.setRange(0, 100)
self.progress_bar.setValue(0)
self.progress_bar.setStyleSheet("""
QProgressBar {
border: none;
border-radius: 4px;
background: #555555;
}
QProgressBar::chunk {
background: #1db954;
border-radius: 4px;
}
""")
progress_layout.addLayout(progress_info_layout)
progress_layout.addWidget(self.progress_bar)
# Statistics section (shows current database info)
stats_group = QGroupBox("Database Statistics")
stats_group.setFont(QFont("Arial", 10, QFont.Weight.Bold))
stats_group.setStyleSheet("""
QGroupBox {
color: #ffffff;
border: 1px solid #555555;
border-radius: 6px;
margin-top: 6px;
padding-top: 10px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 5px 0 5px;
}
""")
stats_layout = QHBoxLayout(stats_group)
stats_layout.setSpacing(20)
# Artists stat
artists_layout = QVBoxLayout()
self.artists_count_label = QLabel("0")
self.artists_count_label.setFont(QFont("Arial", 16, QFont.Weight.Bold))
self.artists_count_label.setStyleSheet("color: #1db954;")
self.artists_count_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
artists_text_label = QLabel("Artists")
artists_text_label.setFont(QFont("Arial", 9))
artists_text_label.setStyleSheet("color: #b3b3b3;")
artists_text_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
artists_layout.addWidget(self.artists_count_label)
artists_layout.addWidget(artists_text_label)
# Albums stat
albums_layout = QVBoxLayout()
self.albums_count_label = QLabel("0")
self.albums_count_label.setFont(QFont("Arial", 16, QFont.Weight.Bold))
self.albums_count_label.setStyleSheet("color: #1db954;")
self.albums_count_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
albums_text_label = QLabel("Albums")
albums_text_label.setFont(QFont("Arial", 9))
albums_text_label.setStyleSheet("color: #b3b3b3;")
albums_text_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
albums_layout.addWidget(self.albums_count_label)
albums_layout.addWidget(albums_text_label)
# Tracks stat
tracks_layout = QVBoxLayout()
self.tracks_count_label = QLabel("0")
self.tracks_count_label.setFont(QFont("Arial", 16, QFont.Weight.Bold))
self.tracks_count_label.setStyleSheet("color: #1db954;")
self.tracks_count_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
tracks_text_label = QLabel("Tracks")
tracks_text_label.setFont(QFont("Arial", 9))
tracks_text_label.setStyleSheet("color: #b3b3b3;")
tracks_text_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
tracks_layout.addWidget(self.tracks_count_label)
tracks_layout.addWidget(tracks_text_label)
# Database size stat
size_layout = QVBoxLayout()
self.size_label = QLabel("0.0 MB")
self.size_label.setFont(QFont("Arial", 16, QFont.Weight.Bold))
self.size_label.setStyleSheet("color: #1db954;")
self.size_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
size_text_label = QLabel("DB Size")
size_text_label.setFont(QFont("Arial", 9))
size_text_label.setStyleSheet("color: #b3b3b3;")
size_text_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
size_layout.addWidget(self.size_label)
size_layout.addWidget(size_text_label)
stats_layout.addLayout(artists_layout)
stats_layout.addLayout(albums_layout)
stats_layout.addLayout(tracks_layout)
stats_layout.addLayout(size_layout)
stats_layout.addStretch()
# 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)
def update_progress(self, is_running: bool, current_item: str, processed: int, total: int, percentage: float):
"""Update progress display during database update"""
if is_running:
self.start_button.setText("Stop Update")
self.start_button.setEnabled(True)
self.current_status_label.setText(current_item if current_item else "Processing...")
self.progress_label.setText(f"Progress: {percentage:.1f}%")
self.count_label.setText(f"{processed} / {total} artists processed")
self.progress_bar.setValue(int(percentage))
else:
self.start_button.setText("Update Database")
self.start_button.setEnabled(True)
self.current_status_label.setText("Ready")
self.progress_label.setText("Progress: 0%")
self.count_label.setText("0 artists processed")
self.progress_bar.setValue(0)
def update_statistics(self, stats: dict):
"""Update database statistics display"""
self.artists_count_label.setText(str(stats.get('artists', 0)))
self.albums_count_label.setText(str(stats.get('albums', 0)))
self.tracks_count_label.setText(str(stats.get('tracks', 0)))
self.size_label.setText(f"{stats.get('database_size_mb', 0.0):.1f} MB")
def update_phase(self, phase: str):
"""Update current phase display"""
self.current_status_label.setText(phase)
def is_full_refresh(self) -> bool:
"""Check if full refresh is selected"""
return self.update_type_combo.currentText() == "Full Refresh"
def set_button_text(self, text: str):
"""Set custom button text"""
self.start_button.setText(text)
def set_button_enabled(self, enabled: bool):
"""Enable/disable the start button"""
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;")