fix
This commit is contained in:
parent
8e52afe57d
commit
36c7e5922e
12 changed files with 508 additions and 74 deletions
|
|
@ -1 +1 @@
|
|||
{"access_token": "BQDHuavPHFxKGfglVQ7zVOIVbF8ufUuQlH_d-mqEsy0PLmbsHR9OxUyB-QVgF_5-Nrbqwni67byBlNFhBg4dwx45A-SCedirLIMu5IIy6CSCszxwjHXLwhc4Kmz3iGbMytpLQpbsDfqTCTHk2xlfo2DY29XM_Be3ToRWv7LWFCadeIOBxjz2td_xRCDezEFjoZ-g1S9dbVO52dOwo2LPvUk4na5HKE8aH7fJ4uhZNDSyg-Isqm54JQGmWUOnGwxI", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM", "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1752090138}
|
||||
{"access_token": "BQA7NSoqYKl8-HgT1MaTChCe-DbSBPXUZG9_4AzQYIqZsO4MgnVjJyl35gsDKR36HejqrJK8DziDO-HIecF2nhX4OSro0ROIY71CCtjCboE_-BAHk7w-x1XtUyINP7ZNBARToEaketFUiguJAMXIH7NALut2AtQelLyuT7X_NZdINBNpUY6St3BoEAiOIJe5dkxy7UFdHgLZdBLDUZhXLs40zB73qONG4Ie3WjkCAH_RTa4owXn7wvSMLToXd3VH", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1752094286, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"}
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"soulseek": {
|
||||
"slskd_url": "http://localhost:5030",
|
||||
"api_key": "",
|
||||
"api_key": "123456789123456789",
|
||||
"download_path": "./downloads"
|
||||
},
|
||||
"database": {
|
||||
|
|
|
|||
59
logs/app.log
59
logs/app.log
|
|
@ -104,3 +104,62 @@
|
|||
2025-07-09 12:28:42 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: downloads
|
||||
2025-07-09 12:28:43 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: artists
|
||||
2025-07-09 12:28:44 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: settings
|
||||
2025-07-09 12:29:52 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: downloads
|
||||
2025-07-09 12:29:53 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: artists
|
||||
2025-07-09 12:29:54 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: dashboard
|
||||
2025-07-09 12:30:23 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: settings
|
||||
2025-07-09 12:30:24 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: artists
|
||||
2025-07-09 12:34:29 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: settings
|
||||
2025-07-09 12:34:56 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: artists
|
||||
2025-07-09 12:34:57 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: downloads
|
||||
2025-07-09 12:36:03 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: sync
|
||||
2025-07-09 12:37:31 - newmusic.main - [92mINFO[0m - closeEvent:152 - Closing application...
|
||||
2025-07-09 12:40:45 - newmusic - [92mINFO[0m - setup_logging:57 - Logging initialized with level: DEBUG
|
||||
2025-07-09 12:40:45 - newmusic.main - [92mINFO[0m - main:173 - Starting NewMusic application
|
||||
2025-07-09 12:40:45 - newmusic.spotify_client - [92mINFO[0m - _setup_client:84 - Successfully authenticated with Spotify as broquethomas
|
||||
2025-07-09 12:40:45 - newmusic.plex_client - [92mINFO[0m - _find_music_library:98 - Found music library: Music
|
||||
2025-07-09 12:40:45 - newmusic.plex_client - [92mINFO[0m - _setup_client:84 - Successfully connected to Plex server: PLEX-MACHINE
|
||||
2025-07-09 12:40:45 - newmusic.soulseek_client - [92mINFO[0m - _setup_client:88 - Soulseek client configured with slskd at http://localhost:5030
|
||||
2025-07-09 12:40:46 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: dashboard
|
||||
2025-07-09 12:40:48 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: sync
|
||||
2025-07-09 12:40:59 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: downloads
|
||||
2025-07-09 12:41:00 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: artists
|
||||
2025-07-09 12:41:41 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: settings
|
||||
2025-07-09 12:42:45 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: dashboard
|
||||
2025-07-09 12:42:47 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: sync
|
||||
2025-07-09 12:43:27 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: settings
|
||||
2025-07-09 12:47:49 - newmusic.main - [92mINFO[0m - closeEvent:152 - Closing application...
|
||||
2025-07-09 12:51:25 - newmusic - [92mINFO[0m - setup_logging:57 - Logging initialized with level: DEBUG
|
||||
2025-07-09 12:51:25 - newmusic.main - [92mINFO[0m - main:173 - Starting NewMusic application
|
||||
2025-07-09 12:51:26 - newmusic.spotify_client - [92mINFO[0m - _setup_client:84 - Successfully authenticated with Spotify as broquethomas
|
||||
2025-07-09 12:51:26 - newmusic.plex_client - [92mINFO[0m - _find_music_library:98 - Found music library: Music
|
||||
2025-07-09 12:51:26 - newmusic.plex_client - [92mINFO[0m - _setup_client:84 - Successfully connected to Plex server: PLEX-MACHINE
|
||||
2025-07-09 12:51:26 - newmusic.soulseek_client - [92mINFO[0m - _setup_client:88 - Soulseek client configured with slskd at http://localhost:5030
|
||||
2025-07-09 12:51:26 - newmusic.spotify_client - [92mINFO[0m - get_user_playlists:106 - Fetching tracks for playlist: Aether
|
||||
2025-07-09 12:51:26 - newmusic.spotify_client - [92mINFO[0m - get_user_playlists:106 - Fetching tracks for playlist: Favorite Artists
|
||||
2025-07-09 12:51:29 - newmusic.spotify_client - [92mINFO[0m - get_user_playlists:106 - Fetching tracks for playlist: Brittnea
|
||||
2025-07-09 12:51:30 - newmusic.spotify_client - [92mINFO[0m - get_user_playlists:106 - Fetching tracks for playlist: Baleigh
|
||||
2025-07-09 12:51:31 - newmusic.spotify_client - [92mINFO[0m - get_user_playlists:106 - Fetching tracks for playlist: Extra Music
|
||||
2025-07-09 12:51:32 - newmusic.spotify_client - [92mINFO[0m - get_user_playlists:106 - Fetching tracks for playlist: Maggi Main
|
||||
2025-07-09 12:51:38 - newmusic.spotify_client - [92mINFO[0m - get_user_playlists:106 - Fetching tracks for playlist: Broque Main
|
||||
2025-07-09 12:51:45 - newmusic.spotify_client - [92mINFO[0m - get_user_playlists:113 - Retrieved 7 playlists
|
||||
2025-07-09 12:58:18 - newmusic - [92mINFO[0m - setup_logging:57 - Logging initialized with level: DEBUG
|
||||
2025-07-09 12:58:18 - newmusic.main - [92mINFO[0m - main:173 - Starting NewMusic application
|
||||
2025-07-09 12:58:18 - newmusic.spotify_client - [92mINFO[0m - _setup_client:84 - Successfully authenticated with Spotify as broquethomas
|
||||
2025-07-09 12:58:18 - newmusic.plex_client - [92mINFO[0m - _find_music_library:98 - Found music library: Music
|
||||
2025-07-09 12:58:18 - newmusic.plex_client - [92mINFO[0m - _setup_client:84 - Successfully connected to Plex server: PLEX-MACHINE
|
||||
2025-07-09 12:58:18 - newmusic.soulseek_client - [92mINFO[0m - _setup_client:88 - Soulseek client configured with slskd at http://localhost:5030
|
||||
2025-07-09 12:58:19 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: dashboard
|
||||
2025-07-09 12:58:19 - newmusic.spotify_client - [92mINFO[0m - get_user_playlists:106 - Fetching tracks for playlist: Aether
|
||||
2025-07-09 12:58:19 - newmusic.spotify_client - [92mINFO[0m - get_user_playlists:106 - Fetching tracks for playlist: Favorite Artists
|
||||
2025-07-09 12:58:22 - newmusic.spotify_client - [92mINFO[0m - get_user_playlists:106 - Fetching tracks for playlist: Brittnea
|
||||
2025-07-09 12:58:23 - newmusic.spotify_client - [92mINFO[0m - get_user_playlists:106 - Fetching tracks for playlist: Baleigh
|
||||
2025-07-09 12:58:24 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: sync
|
||||
2025-07-09 12:58:24 - newmusic.spotify_client - [92mINFO[0m - get_user_playlists:106 - Fetching tracks for playlist: Extra Music
|
||||
2025-07-09 12:58:25 - newmusic.spotify_client - [92mINFO[0m - get_user_playlists:106 - Fetching tracks for playlist: Maggi Main
|
||||
2025-07-09 12:58:30 - newmusic.spotify_client - [92mINFO[0m - get_user_playlists:106 - Fetching tracks for playlist: Broque Main
|
||||
2025-07-09 12:58:37 - newmusic.spotify_client - [92mINFO[0m - get_user_playlists:113 - Retrieved 7 playlists
|
||||
2025-07-09 12:58:50 - newmusic.main - [92mINFO[0m - change_page:139 - Changed to page: settings
|
||||
2025-07-09 12:59:15 - newmusic.plex_client - [92mINFO[0m - _find_music_library:98 - Found music library: Music
|
||||
2025-07-09 12:59:15 - newmusic.plex_client - [92mINFO[0m - _setup_client:84 - Successfully connected to Plex server: PLEX-MACHINE
|
||||
2025-07-09 12:59:17 - newmusic.spotify_client - [92mINFO[0m - _setup_client:84 - Successfully authenticated with Spotify as broquethomas
|
||||
|
|
|
|||
2
main.py
2
main.py
|
|
@ -99,7 +99,7 @@ class MainWindow(QMainWindow):
|
|||
|
||||
# Create and add pages
|
||||
self.dashboard_page = DashboardPage()
|
||||
self.sync_page = SyncPage()
|
||||
self.sync_page = SyncPage(self.spotify_client, self.plex_client)
|
||||
self.downloads_page = DownloadsPage()
|
||||
self.artists_page = ArtistsPage()
|
||||
self.settings_page = SettingsPage()
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -81,6 +81,7 @@ class ArtistCard(QFrame):
|
|||
|
||||
update_btn = QPushButton("Update")
|
||||
update_btn.setFixedSize(60, 25)
|
||||
update_btn.setToolTip("Update metadata for this artist only")
|
||||
update_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: #1db954;
|
||||
|
|
@ -97,6 +98,7 @@ class ArtistCard(QFrame):
|
|||
|
||||
download_btn = QPushButton("Download")
|
||||
download_btn.setFixedSize(60, 25)
|
||||
download_btn.setToolTip("Download complete discography for this artist")
|
||||
download_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: transparent;
|
||||
|
|
@ -177,9 +179,14 @@ class ArtistsPage(QWidget):
|
|||
|
||||
def create_controls(self):
|
||||
controls = QWidget()
|
||||
layout = QHBoxLayout(controls)
|
||||
layout = QVBoxLayout(controls)
|
||||
layout.setSpacing(15)
|
||||
|
||||
# First row - search and filters
|
||||
first_row = QWidget()
|
||||
first_row_layout = QHBoxLayout(first_row)
|
||||
first_row_layout.setSpacing(15)
|
||||
|
||||
# Search bar
|
||||
search_bar = QLineEdit()
|
||||
search_bar.setPlaceholderText("Search artists...")
|
||||
|
|
@ -241,6 +248,7 @@ class ArtistsPage(QWidget):
|
|||
# Update all button
|
||||
update_btn = QPushButton("📝 Update All Metadata")
|
||||
update_btn.setFixedHeight(40)
|
||||
update_btn.setToolTip("Batch update metadata for all artists (skips already completed)")
|
||||
update_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: transparent;
|
||||
|
|
@ -256,14 +264,73 @@ class ArtistsPage(QWidget):
|
|||
}
|
||||
""")
|
||||
|
||||
layout.addWidget(search_bar)
|
||||
layout.addWidget(filter_combo)
|
||||
layout.addStretch()
|
||||
layout.addWidget(scan_btn)
|
||||
layout.addWidget(update_btn)
|
||||
first_row_layout.addWidget(search_bar)
|
||||
first_row_layout.addWidget(filter_combo)
|
||||
first_row_layout.addStretch()
|
||||
first_row_layout.addWidget(scan_btn)
|
||||
first_row_layout.addWidget(update_btn)
|
||||
|
||||
# Second row - alphabet navigation
|
||||
alphabet_row = self.create_alphabet_navigation()
|
||||
|
||||
layout.addWidget(first_row)
|
||||
layout.addWidget(alphabet_row)
|
||||
|
||||
return controls
|
||||
|
||||
def create_alphabet_navigation(self):
|
||||
alphabet_widget = QWidget()
|
||||
alphabet_layout = QHBoxLayout(alphabet_widget)
|
||||
alphabet_layout.setContentsMargins(0, 0, 0, 0)
|
||||
alphabet_layout.setSpacing(3)
|
||||
|
||||
# Create alphabet buttons
|
||||
letters = ['All', '#'] + [chr(i) for i in range(ord('A'), ord('Z')+1)]
|
||||
|
||||
for letter in letters:
|
||||
btn = QPushButton(letter)
|
||||
btn.setFixedSize(30, 30)
|
||||
btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: #282828;
|
||||
border: 1px solid #404040;
|
||||
border-radius: 15px;
|
||||
color: #b3b3b3;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background: #1db954;
|
||||
border: 1px solid #1db954;
|
||||
color: #000000;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background: #1aa34a;
|
||||
}
|
||||
""")
|
||||
|
||||
# Set "All" as active by default
|
||||
if letter == 'All':
|
||||
btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: #1db954;
|
||||
border: 1px solid #1db954;
|
||||
border-radius: 15px;
|
||||
color: #000000;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background: #1ed760;
|
||||
}
|
||||
""")
|
||||
|
||||
alphabet_layout.addWidget(btn)
|
||||
|
||||
alphabet_layout.addStretch()
|
||||
|
||||
return alphabet_widget
|
||||
|
||||
def create_artists_section(self):
|
||||
section = QWidget()
|
||||
layout = QVBoxLayout(section)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QFrame, QPushButton, QLineEdit, QComboBox,
|
||||
QCheckBox, QSpinBox, QTextEdit, QGroupBox, QFormLayout)
|
||||
QCheckBox, QSpinBox, QTextEdit, QGroupBox, QFormLayout, QMessageBox)
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtGui import QFont
|
||||
from config.settings import config_manager
|
||||
|
||||
class SettingsGroup(QGroupBox):
|
||||
def __init__(self, title: str, parent=None):
|
||||
|
|
@ -28,7 +29,10 @@ class SettingsGroup(QGroupBox):
|
|||
class SettingsPage(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.config_manager = None
|
||||
self.form_inputs = {}
|
||||
self.setup_ui()
|
||||
self.load_config_values()
|
||||
|
||||
def setup_ui(self):
|
||||
self.setStyleSheet("""
|
||||
|
|
@ -61,9 +65,10 @@ class SettingsPage(QWidget):
|
|||
main_layout.addStretch()
|
||||
|
||||
# Save button
|
||||
save_btn = QPushButton("💾 Save Settings")
|
||||
save_btn.setFixedHeight(45)
|
||||
save_btn.setStyleSheet("""
|
||||
self.save_btn = QPushButton("💾 Save Settings")
|
||||
self.save_btn.setFixedHeight(45)
|
||||
self.save_btn.clicked.connect(self.save_settings)
|
||||
self.save_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: #1db954;
|
||||
border: none;
|
||||
|
|
@ -77,7 +82,173 @@ class SettingsPage(QWidget):
|
|||
}
|
||||
""")
|
||||
|
||||
main_layout.addWidget(save_btn)
|
||||
main_layout.addWidget(self.save_btn)
|
||||
|
||||
def load_config_values(self):
|
||||
"""Load current configuration values into form inputs"""
|
||||
try:
|
||||
# Load Spotify config
|
||||
spotify_config = config_manager.get_spotify_config()
|
||||
self.client_id_input.setText(spotify_config.get('client_id', ''))
|
||||
self.client_secret_input.setText(spotify_config.get('client_secret', ''))
|
||||
|
||||
# Load Plex config
|
||||
plex_config = config_manager.get_plex_config()
|
||||
self.plex_url_input.setText(plex_config.get('base_url', ''))
|
||||
self.plex_token_input.setText(plex_config.get('token', ''))
|
||||
|
||||
# Load Soulseek config
|
||||
soulseek_config = config_manager.get_soulseek_config()
|
||||
self.slskd_url_input.setText(soulseek_config.get('slskd_url', ''))
|
||||
self.api_key_input.setText(soulseek_config.get('api_key', ''))
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "Error", f"Failed to load configuration: {e}")
|
||||
|
||||
def save_settings(self):
|
||||
"""Save current form values to configuration"""
|
||||
try:
|
||||
# Save Spotify settings
|
||||
config_manager.set('spotify.client_id', self.client_id_input.text())
|
||||
config_manager.set('spotify.client_secret', self.client_secret_input.text())
|
||||
|
||||
# Save Plex settings
|
||||
config_manager.set('plex.base_url', self.plex_url_input.text())
|
||||
config_manager.set('plex.token', self.plex_token_input.text())
|
||||
|
||||
# Save Soulseek settings
|
||||
config_manager.set('soulseek.slskd_url', self.slskd_url_input.text())
|
||||
config_manager.set('soulseek.api_key', self.api_key_input.text())
|
||||
|
||||
# Show success message
|
||||
QMessageBox.information(self, "Success", "Settings saved successfully!")
|
||||
|
||||
# Update button text temporarily
|
||||
original_text = self.save_btn.text()
|
||||
self.save_btn.setText("✓ Saved!")
|
||||
self.save_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: #1aa34a;
|
||||
border: none;
|
||||
border-radius: 22px;
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
|
||||
# Reset button after 2 seconds
|
||||
from PyQt6.QtCore import QTimer
|
||||
QTimer.singleShot(2000, lambda: self.reset_save_button(original_text))
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Error", f"Failed to save settings: {e}")
|
||||
|
||||
def reset_save_button(self, original_text):
|
||||
"""Reset save button to original state"""
|
||||
self.save_btn.setText(original_text)
|
||||
self.save_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: #1db954;
|
||||
border: none;
|
||||
border-radius: 22px;
|
||||
color: #000000;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background: #1ed760;
|
||||
}
|
||||
""")
|
||||
|
||||
def test_spotify_connection(self):
|
||||
"""Test Spotify API connection"""
|
||||
try:
|
||||
from core.spotify_client import SpotifyClient
|
||||
|
||||
# Create temporary client with current form values
|
||||
temp_config = config_manager.get_spotify_config().copy()
|
||||
temp_config['client_id'] = self.client_id_input.text()
|
||||
temp_config['client_secret'] = self.client_secret_input.text()
|
||||
|
||||
# Save temporarily to test
|
||||
original_client_id = config_manager.get('spotify.client_id')
|
||||
original_client_secret = config_manager.get('spotify.client_secret')
|
||||
|
||||
config_manager.set('spotify.client_id', temp_config['client_id'])
|
||||
config_manager.set('spotify.client_secret', temp_config['client_secret'])
|
||||
|
||||
# Test connection
|
||||
client = SpotifyClient()
|
||||
if client.is_authenticated():
|
||||
user_info = client.get_user_info()
|
||||
username = user_info.get('display_name', 'Unknown') if user_info else 'Unknown'
|
||||
QMessageBox.information(self, "Success", f"✓ Spotify connection successful!\nConnected as: {username}")
|
||||
else:
|
||||
QMessageBox.warning(self, "Failed", "✗ Spotify connection failed.\nCheck your credentials and try again.")
|
||||
|
||||
# Restore original values
|
||||
config_manager.set('spotify.client_id', original_client_id)
|
||||
config_manager.set('spotify.client_secret', original_client_secret)
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Error", f"✗ Spotify test failed:\n{str(e)}")
|
||||
|
||||
def test_plex_connection(self):
|
||||
"""Test Plex server connection"""
|
||||
try:
|
||||
from core.plex_client import PlexClient
|
||||
|
||||
# Save temporarily to test
|
||||
original_base_url = config_manager.get('plex.base_url')
|
||||
original_token = config_manager.get('plex.token')
|
||||
|
||||
config_manager.set('plex.base_url', self.plex_url_input.text())
|
||||
config_manager.set('plex.token', self.plex_token_input.text())
|
||||
|
||||
# Test connection
|
||||
client = PlexClient()
|
||||
if client.is_connected():
|
||||
server_name = client.server.friendlyName if client.server else 'Unknown'
|
||||
QMessageBox.information(self, "Success", f"✓ Plex connection successful!\nServer: {server_name}")
|
||||
else:
|
||||
QMessageBox.warning(self, "Failed", "✗ Plex connection failed.\nCheck your server URL and token.")
|
||||
|
||||
# Restore original values
|
||||
config_manager.set('plex.base_url', original_base_url)
|
||||
config_manager.set('plex.token', original_token)
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Error", f"✗ Plex test failed:\n{str(e)}")
|
||||
|
||||
def test_soulseek_connection(self):
|
||||
"""Test Soulseek slskd connection"""
|
||||
try:
|
||||
import requests
|
||||
|
||||
slskd_url = self.slskd_url_input.text()
|
||||
api_key = self.api_key_input.text()
|
||||
|
||||
if not slskd_url:
|
||||
QMessageBox.warning(self, "Error", "Please enter slskd URL")
|
||||
return
|
||||
|
||||
# Test API endpoint
|
||||
headers = {}
|
||||
if api_key:
|
||||
headers['X-API-Key'] = api_key
|
||||
|
||||
response = requests.get(f"{slskd_url}/api/v0/session", headers=headers, timeout=5)
|
||||
|
||||
if response.status_code == 200:
|
||||
QMessageBox.information(self, "Success", "✓ Soulseek connection successful!\nslskd is responding.")
|
||||
else:
|
||||
QMessageBox.warning(self, "Failed", f"✗ Soulseek connection failed.\nHTTP {response.status_code}")
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
QMessageBox.critical(self, "Error", f"✗ Soulseek test failed:\n{str(e)}")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Error", f"✗ Soulseek test failed:\n{str(e)}")
|
||||
|
||||
def create_header(self):
|
||||
header = QWidget()
|
||||
|
|
@ -120,16 +291,18 @@ class SettingsPage(QWidget):
|
|||
spotify_title.setFont(QFont("Arial", 12, QFont.Weight.Bold))
|
||||
spotify_title.setStyleSheet("color: #1db954;")
|
||||
|
||||
client_id_input = QLineEdit("512b25bd9e0d4ecd82140f6d1ce0c8e6")
|
||||
client_id_input.setStyleSheet(self.get_input_style())
|
||||
self.client_id_input = QLineEdit()
|
||||
self.client_id_input.setStyleSheet(self.get_input_style())
|
||||
self.form_inputs['spotify.client_id'] = self.client_id_input
|
||||
|
||||
client_secret_input = QLineEdit("c3844dcbedbc4e09a6242a14b2e89e89")
|
||||
client_secret_input.setEchoMode(QLineEdit.EchoMode.Password)
|
||||
client_secret_input.setStyleSheet(self.get_input_style())
|
||||
self.client_secret_input = QLineEdit()
|
||||
self.client_secret_input.setEchoMode(QLineEdit.EchoMode.Password)
|
||||
self.client_secret_input.setStyleSheet(self.get_input_style())
|
||||
self.form_inputs['spotify.client_secret'] = self.client_secret_input
|
||||
|
||||
spotify_layout.addRow(spotify_title)
|
||||
spotify_layout.addRow("Client ID:", client_id_input)
|
||||
spotify_layout.addRow("Client Secret:", client_secret_input)
|
||||
spotify_layout.addRow("Client ID:", self.client_id_input)
|
||||
spotify_layout.addRow("Client Secret:", self.client_secret_input)
|
||||
|
||||
# Plex settings
|
||||
plex_frame = QFrame()
|
||||
|
|
@ -140,16 +313,18 @@ class SettingsPage(QWidget):
|
|||
plex_title.setFont(QFont("Arial", 12, QFont.Weight.Bold))
|
||||
plex_title.setStyleSheet("color: #e5a00d;")
|
||||
|
||||
plex_url_input = QLineEdit("http://192.168.86.36:32400")
|
||||
plex_url_input.setStyleSheet(self.get_input_style())
|
||||
self.plex_url_input = QLineEdit()
|
||||
self.plex_url_input.setStyleSheet(self.get_input_style())
|
||||
self.form_inputs['plex.base_url'] = self.plex_url_input
|
||||
|
||||
plex_token_input = QLineEdit("a9hTgvasV1aJMLSdoBkr")
|
||||
plex_token_input.setEchoMode(QLineEdit.EchoMode.Password)
|
||||
plex_token_input.setStyleSheet(self.get_input_style())
|
||||
self.plex_token_input = QLineEdit()
|
||||
self.plex_token_input.setEchoMode(QLineEdit.EchoMode.Password)
|
||||
self.plex_token_input.setStyleSheet(self.get_input_style())
|
||||
self.form_inputs['plex.token'] = self.plex_token_input
|
||||
|
||||
plex_layout.addRow(plex_title)
|
||||
plex_layout.addRow("Server URL:", plex_url_input)
|
||||
plex_layout.addRow("Token:", plex_token_input)
|
||||
plex_layout.addRow("Server URL:", self.plex_url_input)
|
||||
plex_layout.addRow("Token:", self.plex_token_input)
|
||||
|
||||
# Soulseek settings
|
||||
soulseek_frame = QFrame()
|
||||
|
|
@ -160,17 +335,19 @@ class SettingsPage(QWidget):
|
|||
soulseek_title.setFont(QFont("Arial", 12, QFont.Weight.Bold))
|
||||
soulseek_title.setStyleSheet("color: #ff6b35;")
|
||||
|
||||
slskd_url_input = QLineEdit("http://localhost:5030")
|
||||
slskd_url_input.setStyleSheet(self.get_input_style())
|
||||
self.slskd_url_input = QLineEdit()
|
||||
self.slskd_url_input.setStyleSheet(self.get_input_style())
|
||||
self.form_inputs['soulseek.slskd_url'] = self.slskd_url_input
|
||||
|
||||
api_key_input = QLineEdit()
|
||||
api_key_input.setPlaceholderText("Enter your slskd API key")
|
||||
api_key_input.setEchoMode(QLineEdit.EchoMode.Password)
|
||||
api_key_input.setStyleSheet(self.get_input_style())
|
||||
self.api_key_input = QLineEdit()
|
||||
self.api_key_input.setPlaceholderText("Enter your slskd API key")
|
||||
self.api_key_input.setEchoMode(QLineEdit.EchoMode.Password)
|
||||
self.api_key_input.setStyleSheet(self.get_input_style())
|
||||
self.form_inputs['soulseek.api_key'] = self.api_key_input
|
||||
|
||||
soulseek_layout.addRow(soulseek_title)
|
||||
soulseek_layout.addRow("slskd URL:", slskd_url_input)
|
||||
soulseek_layout.addRow("API Key:", api_key_input)
|
||||
soulseek_layout.addRow("slskd URL:", self.slskd_url_input)
|
||||
soulseek_layout.addRow("API Key:", self.api_key_input)
|
||||
|
||||
api_layout.addWidget(spotify_frame)
|
||||
api_layout.addWidget(plex_frame)
|
||||
|
|
@ -182,14 +359,17 @@ class SettingsPage(QWidget):
|
|||
|
||||
test_spotify = QPushButton("Test Spotify")
|
||||
test_spotify.setFixedHeight(30)
|
||||
test_spotify.clicked.connect(self.test_spotify_connection)
|
||||
test_spotify.setStyleSheet(self.get_test_button_style())
|
||||
|
||||
test_plex = QPushButton("Test Plex")
|
||||
test_plex.setFixedHeight(30)
|
||||
test_plex.clicked.connect(self.test_plex_connection)
|
||||
test_plex.setStyleSheet(self.get_test_button_style())
|
||||
|
||||
test_soulseek = QPushButton("Test Soulseek")
|
||||
test_soulseek.setFixedHeight(30)
|
||||
test_soulseek.clicked.connect(self.test_soulseek_connection)
|
||||
test_soulseek.setStyleSheet(self.get_test_button_style())
|
||||
|
||||
test_layout.addWidget(test_spotify)
|
||||
|
|
@ -288,19 +468,22 @@ class SettingsPage(QWidget):
|
|||
auto_sync.setChecked(True)
|
||||
auto_sync.setStyleSheet(self.get_checkbox_style())
|
||||
|
||||
# Update metadata checkbox
|
||||
update_metadata = QCheckBox("Update metadata from Spotify")
|
||||
update_metadata.setChecked(True)
|
||||
update_metadata.setStyleSheet(self.get_checkbox_style())
|
||||
# Sync interval
|
||||
interval_layout = QHBoxLayout()
|
||||
interval_label = QLabel("Sync Interval (minutes):")
|
||||
interval_label.setStyleSheet("color: #ffffff; font-size: 12px;")
|
||||
|
||||
# Download missing tracks checkbox
|
||||
download_missing = QCheckBox("Automatically download missing tracks")
|
||||
download_missing.setChecked(False)
|
||||
download_missing.setStyleSheet(self.get_checkbox_style())
|
||||
interval_spin = QSpinBox()
|
||||
interval_spin.setRange(5, 1440) # 5 minutes to 24 hours
|
||||
interval_spin.setValue(60)
|
||||
interval_spin.setStyleSheet(self.get_spin_style())
|
||||
|
||||
interval_layout.addWidget(interval_label)
|
||||
interval_layout.addWidget(interval_spin)
|
||||
interval_layout.addStretch()
|
||||
|
||||
sync_layout.addWidget(auto_sync)
|
||||
sync_layout.addWidget(update_metadata)
|
||||
sync_layout.addWidget(download_missing)
|
||||
sync_layout.addLayout(interval_layout)
|
||||
|
||||
# Logging Settings
|
||||
logging_group = SettingsGroup("Logging Settings")
|
||||
|
|
|
|||
177
ui/pages/sync.py
177
ui/pages/sync.py
|
|
@ -1,10 +1,40 @@
|
|||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QFrame, QPushButton, QListWidget, QListWidgetItem,
|
||||
QProgressBar, QTextEdit, QCheckBox, QComboBox,
|
||||
QScrollArea, QSizePolicy)
|
||||
from PyQt6.QtCore import Qt, QThread, pyqtSignal
|
||||
QScrollArea, QSizePolicy, QMessageBox)
|
||||
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QTimer
|
||||
from PyQt6.QtGui import QFont
|
||||
|
||||
class PlaylistLoaderThread(QThread):
|
||||
playlist_loaded = pyqtSignal(object) # Single playlist
|
||||
loading_finished = pyqtSignal(int) # Total count
|
||||
loading_failed = pyqtSignal(str) # Error message
|
||||
progress_updated = pyqtSignal(str) # Progress text
|
||||
|
||||
def __init__(self, spotify_client):
|
||||
super().__init__()
|
||||
self.spotify_client = spotify_client
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.progress_updated.emit("Connecting to Spotify...")
|
||||
if not self.spotify_client or not self.spotify_client.is_authenticated():
|
||||
self.loading_failed.emit("Spotify not authenticated")
|
||||
return
|
||||
|
||||
self.progress_updated.emit("Fetching playlists...")
|
||||
playlists = self.spotify_client.get_user_playlists()
|
||||
|
||||
for i, playlist in enumerate(playlists):
|
||||
self.progress_updated.emit(f"Loading playlist {i+1}/{len(playlists)}: {playlist.name}")
|
||||
self.playlist_loaded.emit(playlist)
|
||||
self.msleep(50) # Small delay to show progressive loading
|
||||
|
||||
self.loading_finished.emit(len(playlists))
|
||||
|
||||
except Exception as e:
|
||||
self.loading_failed.emit(str(e))
|
||||
|
||||
class PlaylistItem(QFrame):
|
||||
def __init__(self, name: str, track_count: int, sync_status: str, parent=None):
|
||||
super().__init__(parent)
|
||||
|
|
@ -191,9 +221,16 @@ class SyncOptionsPanel(QFrame):
|
|||
layout.addLayout(quality_layout)
|
||||
|
||||
class SyncPage(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, spotify_client=None, plex_client=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.spotify_client = spotify_client
|
||||
self.plex_client = plex_client
|
||||
self.current_playlists = []
|
||||
self.playlist_loader = None
|
||||
self.setup_ui()
|
||||
|
||||
# Start loading playlists asynchronously after UI is ready
|
||||
QTimer.singleShot(100, self.load_playlists_async)
|
||||
|
||||
def setup_ui(self):
|
||||
self.setStyleSheet("""
|
||||
|
|
@ -261,9 +298,10 @@ class SyncPage(QWidget):
|
|||
section_title.setFont(QFont("Arial", 16, QFont.Weight.Bold))
|
||||
section_title.setStyleSheet("color: #ffffff;")
|
||||
|
||||
refresh_btn = QPushButton("🔄 Refresh")
|
||||
refresh_btn.setFixedSize(100, 35)
|
||||
refresh_btn.setStyleSheet("""
|
||||
self.refresh_btn = QPushButton("🔄 Refresh")
|
||||
self.refresh_btn.setFixedSize(100, 35)
|
||||
self.refresh_btn.clicked.connect(self.refresh_playlists)
|
||||
self.refresh_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: #1db954;
|
||||
border: none;
|
||||
|
|
@ -282,7 +320,7 @@ class SyncPage(QWidget):
|
|||
|
||||
header_layout.addWidget(section_title)
|
||||
header_layout.addStretch()
|
||||
header_layout.addWidget(refresh_btn)
|
||||
header_layout.addWidget(self.refresh_btn)
|
||||
|
||||
# Playlist container
|
||||
playlist_container = QScrollArea()
|
||||
|
|
@ -303,26 +341,14 @@ class SyncPage(QWidget):
|
|||
}
|
||||
""")
|
||||
|
||||
playlist_widget = QWidget()
|
||||
playlist_layout = QVBoxLayout(playlist_widget)
|
||||
playlist_layout.setSpacing(10)
|
||||
self.playlist_widget = QWidget()
|
||||
self.playlist_layout = QVBoxLayout(self.playlist_widget)
|
||||
self.playlist_layout.setSpacing(10)
|
||||
|
||||
# Sample playlists
|
||||
playlists = [
|
||||
("Liked Songs", 247, "Synced"),
|
||||
("Discover Weekly", 30, "Needs Sync"),
|
||||
("Chill Vibes", 89, "Synced"),
|
||||
("Workout Mix", 156, "Needs Sync"),
|
||||
("Road Trip", 67, "Never Synced"),
|
||||
("Focus Music", 45, "Synced")
|
||||
]
|
||||
# Playlists will be loaded asynchronously after UI setup
|
||||
|
||||
for name, count, status in playlists:
|
||||
item = PlaylistItem(name, count, status)
|
||||
playlist_layout.addWidget(item)
|
||||
|
||||
playlist_layout.addStretch()
|
||||
playlist_container.setWidget(playlist_widget)
|
||||
self.playlist_layout.addStretch()
|
||||
playlist_container.setWidget(self.playlist_widget)
|
||||
|
||||
layout.addLayout(header_layout)
|
||||
layout.addWidget(playlist_container)
|
||||
|
|
@ -455,4 +481,103 @@ class SyncPage(QWidget):
|
|||
layout.addWidget(progress_header)
|
||||
layout.addWidget(self.progress_bar)
|
||||
layout.addWidget(self.progress_text)
|
||||
layout.addWidget(self.log_area)
|
||||
layout.addWidget(self.log_area)
|
||||
|
||||
return section
|
||||
|
||||
def load_playlists_async(self):
|
||||
"""Start asynchronous playlist loading"""
|
||||
if self.playlist_loader and self.playlist_loader.isRunning():
|
||||
return
|
||||
|
||||
# Clear existing playlists
|
||||
self.clear_playlists()
|
||||
|
||||
# Show loading state
|
||||
self.refresh_btn.setText("🔄 Loading...")
|
||||
self.refresh_btn.setEnabled(False)
|
||||
self.log_area.append("Starting playlist loading...")
|
||||
|
||||
# Create and start loader thread
|
||||
self.playlist_loader = PlaylistLoaderThread(self.spotify_client)
|
||||
self.playlist_loader.playlist_loaded.connect(self.add_playlist_to_ui)
|
||||
self.playlist_loader.loading_finished.connect(self.on_loading_finished)
|
||||
self.playlist_loader.loading_failed.connect(self.on_loading_failed)
|
||||
self.playlist_loader.progress_updated.connect(self.update_progress)
|
||||
self.playlist_loader.start()
|
||||
|
||||
def add_playlist_to_ui(self, playlist):
|
||||
"""Add a single playlist to the UI as it's loaded"""
|
||||
# Simple sync status (placeholder for now)
|
||||
sync_status = "Never Synced" # TODO: Check actual sync status
|
||||
item = PlaylistItem(playlist.name, playlist.total_tracks, sync_status)
|
||||
# Insert before the stretch item
|
||||
self.playlist_layout.insertWidget(self.playlist_layout.count() - 1, item)
|
||||
self.current_playlists.append(playlist)
|
||||
|
||||
# Update log
|
||||
self.log_area.append(f"Added playlist: {playlist.name} ({playlist.total_tracks} tracks)")
|
||||
|
||||
def on_loading_finished(self, count):
|
||||
"""Handle completion of playlist loading"""
|
||||
self.refresh_btn.setText("🔄 Refresh")
|
||||
self.refresh_btn.setEnabled(True)
|
||||
self.log_area.append(f"✓ Loaded {count} Spotify playlists successfully")
|
||||
|
||||
def on_loading_failed(self, error_msg):
|
||||
"""Handle playlist loading failure"""
|
||||
self.refresh_btn.setText("🔄 Refresh")
|
||||
self.refresh_btn.setEnabled(True)
|
||||
self.log_area.append(f"✗ Failed to load playlists: {error_msg}")
|
||||
QMessageBox.critical(self, "Error", f"Failed to load playlists: {error_msg}")
|
||||
|
||||
def update_progress(self, message):
|
||||
"""Update progress text"""
|
||||
self.log_area.append(message)
|
||||
|
||||
def load_initial_playlists(self):
|
||||
"""Load initial playlist data (placeholder or real)"""
|
||||
if self.spotify_client and self.spotify_client.is_authenticated():
|
||||
self.refresh_playlists()
|
||||
else:
|
||||
# Show placeholder playlists
|
||||
playlists = [
|
||||
("Liked Songs", 247, "Synced"),
|
||||
("Discover Weekly", 30, "Needs Sync"),
|
||||
("Chill Vibes", 89, "Synced"),
|
||||
("Workout Mix", 156, "Needs Sync"),
|
||||
("Road Trip", 67, "Never Synced"),
|
||||
("Focus Music", 45, "Synced")
|
||||
]
|
||||
|
||||
for name, count, status in playlists:
|
||||
item = PlaylistItem(name, count, status)
|
||||
self.playlist_layout.addWidget(item)
|
||||
|
||||
def refresh_playlists(self):
|
||||
"""Refresh playlists from Spotify API using async loader"""
|
||||
if not self.spotify_client:
|
||||
QMessageBox.warning(self, "Error", "Spotify client not available")
|
||||
return
|
||||
|
||||
if not self.spotify_client.is_authenticated():
|
||||
QMessageBox.warning(self, "Error", "Spotify not authenticated. Please check your settings.")
|
||||
return
|
||||
|
||||
# Use the async loader
|
||||
self.load_playlists_async()
|
||||
|
||||
def clear_playlists(self):
|
||||
"""Clear all playlist items from the layout"""
|
||||
# Clear the current playlists list
|
||||
self.current_playlists = []
|
||||
|
||||
# Remove all items except the stretch
|
||||
for i in reversed(range(self.playlist_layout.count())):
|
||||
item = self.playlist_layout.itemAt(i)
|
||||
if item.widget():
|
||||
item.widget().deleteLater()
|
||||
elif item.spacerItem():
|
||||
continue # Keep the stretch spacer
|
||||
else:
|
||||
self.playlist_layout.removeItem(item)
|
||||
|
|
@ -63,7 +63,7 @@ class SidebarButton(QPushButton):
|
|||
transform: scale(1.02);
|
||||
}
|
||||
""")
|
||||
self.text_label.setStyleSheet("color: #1db954; font-weight: bold;")
|
||||
self.text_label.setStyleSheet("color: #1db954; font-weight: bold; background: transparent;")
|
||||
self.icon_label.setStyleSheet("""
|
||||
QLabel {
|
||||
color: #1db954;
|
||||
|
|
@ -87,7 +87,7 @@ class SidebarButton(QPushButton):
|
|||
border-left: 2px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
""")
|
||||
self.text_label.setStyleSheet("color: #b3b3b3;")
|
||||
self.text_label.setStyleSheet("color: #b3b3b3; background: transparent;")
|
||||
self.icon_label.setStyleSheet("""
|
||||
QLabel {
|
||||
color: #b3b3b3;
|
||||
|
|
|
|||
Loading…
Reference in a new issue