From 684433d857d35e978ec5291c4a840ad496fe6f62 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Wed, 6 Aug 2025 00:07:12 -0700 Subject: [PATCH] update variations --- .../database_update_worker.cpython-312.pyc | Bin 12228 -> 19477 bytes core/database_update_worker.py | 7 +- .../music_database.cpython-312.pyc | Bin 23050 -> 24323 bytes database/music_database.py | 194 +++++++++--------- ui/pages/__pycache__/settings.cpython-312.pyc | Bin 66517 -> 68694 bytes ui/pages/settings.py | 43 ++++ 6 files changed, 147 insertions(+), 97 deletions(-) diff --git a/core/__pycache__/database_update_worker.cpython-312.pyc b/core/__pycache__/database_update_worker.cpython-312.pyc index 79c01f31d3758d9852005ba710086c86d3bd9352..b84fec2ea2524b871655396462f96e720f1e4db5 100644 GIT binary patch delta 8657 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 diff --git a/core/database_update_worker.py b/core/database_update_worker.py index 5c81ed25..29a86cb0 100644 --- a/core/database_update_worker.py +++ b/core/database_update_worker.py @@ -9,6 +9,7 @@ import time from database import get_database, MusicDatabase from utils.logging_config import get_logger +from config.settings import config_manager logger = get_logger("database_update_worker") @@ -36,8 +37,10 @@ class DatabaseUpdateWorker(QThread): self.successful_operations = 0 self.failed_operations = 0 - # Threading control - self.max_workers = 3 # Conservative to avoid overwhelming Plex API + # Threading control - get from config or default to 5 + database_config = config_manager.get('database', {}) + self.max_workers = database_config.get('max_workers', 5) + logger.info(f"Using {self.max_workers} worker threads for database update") self.thread_lock = threading.Lock() # Database instance diff --git a/database/__pycache__/music_database.cpython-312.pyc b/database/__pycache__/music_database.cpython-312.pyc index 82c3f9b7bfceae6e142d0b9a2cb8c6f40a6fac5c..b7e876adbe9edc9124c5bbaa62debf339843db1e 100644 GIT binary patch delta 4747 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 I4rx9A1-D)-f&c&j diff --git a/database/music_database.py b/database/music_database.py index 58ffa6c0..338e7165 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -54,19 +54,19 @@ class MusicDatabase: def __init__(self, database_path: str = "database/music_library.db"): self.database_path = Path(database_path) self.database_path.parent.mkdir(parents=True, exist_ok=True) - self._connection: Optional[sqlite3.Connection] = None # Initialize database self._initialize_database() def _get_connection(self) -> sqlite3.Connection: - """Get database connection with row factory""" - if self._connection is None: - self._connection = sqlite3.connect(str(self.database_path)) - self._connection.row_factory = sqlite3.Row - # Enable foreign key constraints - self._connection.execute("PRAGMA foreign_keys = ON") - return self._connection + """Get a NEW database connection for each operation (thread-safe)""" + connection = sqlite3.connect(str(self.database_path), timeout=30.0) + connection.row_factory = sqlite3.Row + # Enable foreign key constraints and WAL mode for better concurrency + connection.execute("PRAGMA foreign_keys = ON") + connection.execute("PRAGMA journal_mode = WAL") + connection.execute("PRAGMA busy_timeout = 30000") # 30 second timeout + return connection def _initialize_database(self): """Create database tables if they don't exist""" @@ -138,31 +138,30 @@ class MusicDatabase: raise def close(self): - """Close database connection""" - if self._connection: - self._connection.close() - self._connection = None + """Close database connection (no-op since we create connections per operation)""" + # Each operation creates and closes its own connection, so nothing to do here + pass def get_statistics(self) -> Dict[str, int]: """Get database statistics""" try: - conn = self._get_connection() - cursor = conn.cursor() - - cursor.execute("SELECT COUNT(*) FROM artists") - artist_count = cursor.fetchone()[0] - - cursor.execute("SELECT COUNT(*) FROM albums") - album_count = cursor.fetchone()[0] - - cursor.execute("SELECT COUNT(*) FROM tracks") - track_count = cursor.fetchone()[0] - - return { - 'artists': artist_count, - 'albums': album_count, - 'tracks': track_count - } + with self._get_connection() as conn: + cursor = conn.cursor() + + cursor.execute("SELECT COUNT(*) FROM artists") + artist_count = cursor.fetchone()[0] + + cursor.execute("SELECT COUNT(*) FROM albums") + album_count = cursor.fetchone()[0] + + cursor.execute("SELECT COUNT(*) FROM tracks") + track_count = cursor.fetchone()[0] + + return { + 'artists': artist_count, + 'albums': album_count, + 'tracks': track_count + } except Exception as e: logger.error(f"Error getting database statistics: {e}") return {'artists': 0, 'albums': 0, 'tracks': 0} @@ -170,16 +169,21 @@ class MusicDatabase: def clear_all_data(self): """Clear all data from database (for full refresh)""" try: - conn = self._get_connection() - cursor = conn.cursor() - - cursor.execute("DELETE FROM tracks") - cursor.execute("DELETE FROM albums") - cursor.execute("DELETE FROM artists") - - conn.commit() - logger.info("All database data cleared") - + with self._get_connection() as conn: + cursor = conn.cursor() + + cursor.execute("DELETE FROM tracks") + cursor.execute("DELETE FROM albums") + cursor.execute("DELETE FROM artists") + + conn.commit() + + # VACUUM to actually shrink the database file and reclaim disk space + logger.info("Vacuuming database to reclaim disk space...") + cursor.execute("VACUUM") + + logger.info("All database data cleared and file compacted") + except Exception as e: logger.error(f"Error clearing database: {e}") raise @@ -188,43 +192,43 @@ class MusicDatabase: def insert_or_update_artist(self, plex_artist) -> bool: """Insert or update artist from Plex artist object""" try: - conn = self._get_connection() - cursor = conn.cursor() - - artist_id = int(plex_artist.ratingKey) - name = plex_artist.title - thumb_url = getattr(plex_artist, 'thumb', None) - summary = getattr(plex_artist, 'summary', None) - - # Get genres - genres = [] - if hasattr(plex_artist, 'genres') and plex_artist.genres: - genres = [genre.tag if hasattr(genre, 'tag') else str(genre) - for genre in plex_artist.genres] - - genres_json = json.dumps(genres) if genres else None - - # Check if artist exists - cursor.execute("SELECT id FROM artists WHERE id = ?", (artist_id,)) - exists = cursor.fetchone() - - if exists: - # Update existing artist - cursor.execute(""" - UPDATE artists - SET name = ?, thumb_url = ?, genres = ?, summary = ?, updated_at = CURRENT_TIMESTAMP - WHERE id = ? - """, (name, thumb_url, genres_json, summary, artist_id)) - else: - # Insert new artist - cursor.execute(""" - INSERT INTO artists (id, name, thumb_url, genres, summary) - VALUES (?, ?, ?, ?, ?) - """, (artist_id, name, thumb_url, genres_json, summary)) - - conn.commit() - return True - + with self._get_connection() as conn: + cursor = conn.cursor() + + artist_id = int(plex_artist.ratingKey) + name = plex_artist.title + thumb_url = getattr(plex_artist, 'thumb', None) + summary = getattr(plex_artist, 'summary', None) + + # Get genres + genres = [] + if hasattr(plex_artist, 'genres') and plex_artist.genres: + genres = [genre.tag if hasattr(genre, 'tag') else str(genre) + for genre in plex_artist.genres] + + genres_json = json.dumps(genres) if genres else None + + # Check if artist exists + cursor.execute("SELECT id FROM artists WHERE id = ?", (artist_id,)) + exists = cursor.fetchone() + + if exists: + # Update existing artist + cursor.execute(""" + UPDATE artists + SET name = ?, thumb_url = ?, genres = ?, summary = ?, updated_at = CURRENT_TIMESTAMP + WHERE id = ? + """, (name, thumb_url, genres_json, summary, artist_id)) + else: + # Insert new artist + cursor.execute(""" + INSERT INTO artists (id, name, thumb_url, genres, summary) + VALUES (?, ?, ?, ?, ?) + """, (artist_id, name, thumb_url, genres_json, summary)) + + conn.commit() + return True + except Exception as e: logger.error(f"Error inserting/updating artist {getattr(plex_artist, 'title', 'Unknown')}: {e}") return False @@ -232,25 +236,25 @@ class MusicDatabase: def get_artist(self, artist_id: int) -> Optional[DatabaseArtist]: """Get artist by ID""" try: - conn = self._get_connection() - cursor = conn.cursor() - - cursor.execute("SELECT * FROM artists WHERE id = ?", (artist_id,)) - row = cursor.fetchone() - - if row: - genres = json.loads(row['genres']) if row['genres'] else None - return DatabaseArtist( - id=row['id'], - name=row['name'], - thumb_url=row['thumb_url'], - genres=genres, - summary=row['summary'], - created_at=datetime.fromisoformat(row['created_at']) if row['created_at'] else None, - updated_at=datetime.fromisoformat(row['updated_at']) if row['updated_at'] else None - ) - return None - + with self._get_connection() as conn: + cursor = conn.cursor() + + cursor.execute("SELECT * FROM artists WHERE id = ?", (artist_id,)) + row = cursor.fetchone() + + if row: + genres = json.loads(row['genres']) if row['genres'] else None + return DatabaseArtist( + id=row['id'], + name=row['name'], + thumb_url=row['thumb_url'], + genres=genres, + summary=row['summary'], + created_at=datetime.fromisoformat(row['created_at']) if row['created_at'] else None, + updated_at=datetime.fromisoformat(row['updated_at']) if row['updated_at'] else None + ) + return None + except Exception as e: logger.error(f"Error getting artist {artist_id}: {e}") return None diff --git a/ui/pages/__pycache__/settings.cpython-312.pyc b/ui/pages/__pycache__/settings.cpython-312.pyc index bbcbd7689fbb82b6482fccc8faadb40dc6266092..d29e439e176d7c6b4e0365243d7a70fb54c35d5f 100644 GIT binary patch delta 3813 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 0: + self.max_workers_combo.setCurrentIndex(index) + # Load logging config (read-only display) logging_config = config_manager.get_logging_config() if hasattr(self, 'log_level_display'): @@ -590,6 +599,11 @@ class SettingsPage(QWidget): config_manager.set('soulseek.download_path', self.download_path_input.text()) config_manager.set('soulseek.transfer_path', self.transfer_path_input.text()) + # Save Database settings + if hasattr(self, 'max_workers_combo'): + max_workers = int(self.max_workers_combo.currentText()) + config_manager.set('database.max_workers', max_workers) + # Emit signals for path changes to update other pages immediately self.settings_changed.emit('soulseek.download_path', self.download_path_input.text()) self.settings_changed.emit('soulseek.transfer_path', self.transfer_path_input.text()) @@ -1257,6 +1271,34 @@ class SettingsPage(QWidget): download_layout.addLayout(path_container) download_layout.addLayout(transfer_path_container) + # Database Settings + database_group = SettingsGroup("Database Settings") + database_layout = QVBoxLayout(database_group) + database_layout.setContentsMargins(16, 20, 16, 16) + database_layout.setSpacing(12) + + # Max Workers + workers_layout = QHBoxLayout() + workers_label = QLabel("Concurrent Workers:") + workers_label.setStyleSheet("color: #ffffff; font-size: 12px;") + + self.max_workers_combo = QComboBox() + self.max_workers_combo.addItems(["3", "4", "5", "6", "7", "8", "9", "10"]) + self.max_workers_combo.setCurrentText("5") # Default value + self.max_workers_combo.setStyleSheet(self.get_combo_style()) + self.max_workers_combo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + + workers_layout.addWidget(workers_label) + workers_layout.addWidget(self.max_workers_combo) + + # Help text for workers + workers_help = QLabel("Number of parallel threads for database updates. Higher values = faster updates but more server load.") + workers_help.setStyleSheet("color: #888888; font-size: 10px; font-style: italic;") + workers_help.setWordWrap(True) + + database_layout.addLayout(workers_layout) + database_layout.addWidget(workers_help) + # Logging Settings logging_group = SettingsGroup("Logging Settings") logging_layout = QVBoxLayout(logging_group) @@ -1305,6 +1347,7 @@ class SettingsPage(QWidget): logging_layout.addLayout(log_path_container) layout.addWidget(download_group) + layout.addWidget(database_group) layout.addWidget(logging_group) layout.addStretch() # Push content to top, prevent stretching