This commit is contained in:
Broque Thomas 2025-07-10 09:03:54 -07:00
parent 96a0bc4b43
commit f647df1ebe
5 changed files with 733 additions and 238 deletions

View file

@ -294,7 +294,7 @@ class SoulseekClient:
logger.error(f"Error in progress callback: {e}")
# Early termination if we have enough results
if len(all_results) >= 100: # Stop after 100 results for better performance
if len(all_results) >= 45: # Stop after 45 results for better performance (3 pages of 15)
logger.info(f"Early termination: Found {len(all_results)} results, stopping search")
break
elif len(all_results) > 0:

View file

@ -5568,3 +5568,348 @@
2025-07-10 08:02:53 - newmusic.soulseek_client - INFO - search:288 - Found 418 new results (total: 418) at 15.0s
2025-07-10 08:02:53 - newmusic.soulseek_client - INFO - search:298 - Early termination: Found 418 results, stopping search
2025-07-10 08:02:53 - newmusic.soulseek_client - INFO - search:309 - Search completed. Found 418 total results for query: Virtual Mage
2025-07-10 08:27:25 - newmusic.main - INFO - closeEvent:152 - Closing application...
2025-07-10 08:27:25 - newmusic.main - INFO - closeEvent:157 - Cleaning up Downloads page threads...
2025-07-10 08:27:25 - newmusic.main - INFO - closeEvent:162 - Stopping status monitoring thread...
2025-07-10 08:27:26 - newmusic.main - INFO - closeEvent:167 - Closing Soulseek client...
2025-07-10 08:27:26 - newmusic.main - INFO - closeEvent:173 - Application closed successfully
2025-07-10 08:27:30 - newmusic - INFO - setup_logging:57 - Logging initialized with level: DEBUG
2025-07-10 08:27:30 - newmusic.main - INFO - main:187 - Starting NewMusic application
2025-07-10 08:27:30 - newmusic.spotify_client - INFO - _setup_client:84 - Successfully authenticated with Spotify as broquethomas
2025-07-10 08:27:31 - newmusic.plex_client - INFO - _find_music_library:98 - Found music library: Music
2025-07-10 08:27:31 - newmusic.plex_client - INFO - _setup_client:84 - Successfully connected to Plex server: PLEX-MACHINE
2025-07-10 08:27:31 - newmusic.soulseek_client - INFO - _setup_client:87 - Soulseek client configured with slskd at http://localhost:5030
2025-07-10 08:27:32 - newmusic.main - INFO - change_page:139 - Changed to page: dashboard
2025-07-10 08:27:32 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Aether
2025-07-10 08:27:32 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Favorite Artists
2025-07-10 08:27:35 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Brittnea
2025-07-10 08:27:37 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Baleigh
2025-07-10 08:27:39 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Extra Music
2025-07-10 08:27:39 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Maggi Main
2025-07-10 08:27:45 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Broque Main
2025-07-10 08:27:53 - newmusic.spotify_client - INFO - get_user_playlists:113 - Retrieved 7 playlists
2025-07-10 08:27:55 - newmusic.main - INFO - change_page:139 - Changed to page: downloads
2025-07-10 08:28:03 - newmusic.soulseek_client - INFO - search:243 - Starting search for: 'virtual mage'
2025-07-10 08:28:03 - newmusic.soulseek_client - DEBUG - search:253 - Search data: {'searchText': 'virtual mage', 'timeout': 30000, 'filterResponses': True, 'minimumResponseFileCount': 1, 'minimumPeerUploadSpeed': 0}
2025-07-10 08:28:03 - newmusic.soulseek_client - DEBUG - search:254 - Making POST request to: http://localhost:5030/api/v0/searches
2025-07-10 08:28:03 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making POST request to: http://localhost:5030/api/v0/searches
2025-07-10 08:28:03 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:28:03 - newmusic.soulseek_client - DEBUG - _make_request:112 - JSON payload: {'searchText': 'virtual mage', 'timeout': 30000, 'filterResponses': True, 'minimumResponseFileCount': 1, 'minimumPeerUploadSpeed': 0}
2025-07-10 08:28:04 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:28:04 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: {"fileCount":0,"id":"ced78c9d-f1b4-491a-9595-d2c9ec9c3509","isComplete":false,"lockedFileCount":0,"responseCount":0,"responses":[],"searchText":"virtual mage","startedAt":"2025-07-10T15:28:04.1760975Z","state":"InProgress","token":136}...
2025-07-10 08:28:04 - newmusic.soulseek_client - INFO - search:267 - Search initiated with ID: ced78c9d-f1b4-491a-9595-d2c9ec9c3509
2025-07-10 08:28:04 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 1/20) - elapsed: 0.0s
2025-07-10 08:28:04 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/ced78c9d-f1b4-491a-9595-d2c9ec9c3509/responses
2025-07-10 08:28:04 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:28:04 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:28:04 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:28:06 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 2/20) - elapsed: 1.5s
2025-07-10 08:28:06 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/ced78c9d-f1b4-491a-9595-d2c9ec9c3509/responses
2025-07-10 08:28:06 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:28:06 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:28:06 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:28:07 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 3/20) - elapsed: 3.0s
2025-07-10 08:28:07 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/ced78c9d-f1b4-491a-9595-d2c9ec9c3509/responses
2025-07-10 08:28:07 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:28:08 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:28:08 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:28:09 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 4/20) - elapsed: 4.5s
2025-07-10 08:28:09 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/ced78c9d-f1b4-491a-9595-d2c9ec9c3509/responses
2025-07-10 08:28:09 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:28:09 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:28:09 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:28:11 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 5/20) - elapsed: 6.0s
2025-07-10 08:28:11 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/ced78c9d-f1b4-491a-9595-d2c9ec9c3509/responses
2025-07-10 08:28:11 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:28:11 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:28:11 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:28:13 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 6/20) - elapsed: 7.5s
2025-07-10 08:28:13 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/ced78c9d-f1b4-491a-9595-d2c9ec9c3509/responses
2025-07-10 08:28:13 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:28:13 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:28:13 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:28:15 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 7/20) - elapsed: 9.0s
2025-07-10 08:28:15 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/ced78c9d-f1b4-491a-9595-d2c9ec9c3509/responses
2025-07-10 08:28:15 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:28:15 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:28:15 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:28:16 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 8/20) - elapsed: 10.5s
2025-07-10 08:28:16 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/ced78c9d-f1b4-491a-9595-d2c9ec9c3509/responses
2025-07-10 08:28:16 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:28:17 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:28:17 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:28:18 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 9/20) - elapsed: 12.0s
2025-07-10 08:28:18 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/ced78c9d-f1b4-491a-9595-d2c9ec9c3509/responses
2025-07-10 08:28:18 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:28:19 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:28:19 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:28:20 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 10/20) - elapsed: 13.5s
2025-07-10 08:28:20 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/ced78c9d-f1b4-491a-9595-d2c9ec9c3509/responses
2025-07-10 08:28:20 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:28:20 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:28:20 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 11/20) - elapsed: 15.0s
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/ced78c9d-f1b4-491a-9595-d2c9ec9c3509/responses
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: [{"fileCount":3,"files":[{"bitRate":183,"code":1,"extension":"","filename":"Music\\Vaporwave\\Vapor Vault\\virtual-internet-prison-vhs-wkvbyn\\BOSEBY - Virtual Internet Prison (VHS) - 02 Temple Vision.vhs Featuring Wave Mage.mp3","isVariableBitRate":true,"length":186,"size":4348704,"isLocked":false},{"bitRate":172,"code":1,"extension":"","filename":"Music\\Vaporwave\\Vapor Vault\\virtual-internet-prison-vhs-wkvbyn\\BOSEBY - Virtual Internet Prison (VHS) - 05 SD.vhs Featuring PKSkyler, CentaurSap...
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:208 - Processing 24 user responses
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User koalabeer has 3 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User Ultravod has 9 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User Letham has 3 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User Kaya-Sem has 1 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User raspberry2 has 10 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User Satani-Vitales has 3 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User RuuqoHoosk has 346 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User soulshookt has 3 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User buk4 has 1 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User zeroscan has 2 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User exsanguinidraculae has 2 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User nightmare_dronemk12 has 3 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User wonko1987 has 2 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User KindaRatchet has 1 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User HomotopicCensorship has 3 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User belthane has 3 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User Shooby has 6 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User aajzugikamlzcr has 1 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User ruszok has 1 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User leonardoclk has 3 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User Dax_VR has 10 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User thinkibettergo has 1 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User FixxAlish has 1 files
2025-07-10 08:28:22 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User ofoijacussa has 0 files
2025-07-10 08:28:22 - newmusic.soulseek_client - INFO - search:288 - Found 418 new results (total: 418) at 15.0s
2025-07-10 08:28:22 - newmusic.soulseek_client - INFO - search:298 - Early termination: Found 418 results, stopping search
2025-07-10 08:28:22 - newmusic.soulseek_client - INFO - search:309 - Search completed. Found 418 total results for query: virtual mage
2025-07-10 08:30:59 - newmusic.main - INFO - change_page:139 - Changed to page: artists
2025-07-10 08:30:59 - newmusic.main - INFO - change_page:139 - Changed to page: downloads
2025-07-10 08:35:14 - newmusic.main - INFO - closeEvent:152 - Closing application...
2025-07-10 08:35:14 - newmusic.main - INFO - closeEvent:157 - Cleaning up Downloads page threads...
2025-07-10 08:35:14 - newmusic.main - INFO - closeEvent:162 - Stopping status monitoring thread...
2025-07-10 08:35:15 - newmusic.main - INFO - closeEvent:167 - Closing Soulseek client...
2025-07-10 08:35:15 - newmusic.main - INFO - closeEvent:173 - Application closed successfully
2025-07-10 08:35:54 - newmusic - INFO - setup_logging:57 - Logging initialized with level: DEBUG
2025-07-10 08:35:54 - newmusic.main - INFO - main:187 - Starting NewMusic application
2025-07-10 08:35:54 - newmusic.spotify_client - INFO - _setup_client:84 - Successfully authenticated with Spotify as broquethomas
2025-07-10 08:35:54 - newmusic.plex_client - INFO - _find_music_library:98 - Found music library: Music
2025-07-10 08:35:54 - newmusic.plex_client - INFO - _setup_client:84 - Successfully connected to Plex server: PLEX-MACHINE
2025-07-10 08:35:54 - newmusic.soulseek_client - INFO - _setup_client:87 - Soulseek client configured with slskd at http://localhost:5030
2025-07-10 08:35:55 - newmusic.main - INFO - change_page:139 - Changed to page: dashboard
2025-07-10 08:35:56 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Aether
2025-07-10 08:35:56 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Favorite Artists
2025-07-10 08:35:59 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Brittnea
2025-07-10 08:36:00 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Baleigh
2025-07-10 08:36:02 - newmusic.main - INFO - change_page:139 - Changed to page: downloads
2025-07-10 08:36:02 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Extra Music
2025-07-10 08:36:03 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Maggi Main
2025-07-10 08:36:05 - newmusic.soulseek_client - INFO - search:243 - Starting search for: 'virtual mage'
2025-07-10 08:36:05 - newmusic.soulseek_client - DEBUG - search:253 - Search data: {'searchText': 'virtual mage', 'timeout': 30000, 'filterResponses': True, 'minimumResponseFileCount': 1, 'minimumPeerUploadSpeed': 0}
2025-07-10 08:36:05 - newmusic.soulseek_client - DEBUG - search:254 - Making POST request to: http://localhost:5030/api/v0/searches
2025-07-10 08:36:05 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making POST request to: http://localhost:5030/api/v0/searches
2025-07-10 08:36:05 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:36:05 - newmusic.soulseek_client - DEBUG - _make_request:112 - JSON payload: {'searchText': 'virtual mage', 'timeout': 30000, 'filterResponses': True, 'minimumResponseFileCount': 1, 'minimumPeerUploadSpeed': 0}
2025-07-10 08:36:05 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:36:05 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: {"fileCount":0,"id":"98191a41-4791-4d60-a8dc-29016f6a8f2f","isComplete":false,"lockedFileCount":0,"responseCount":0,"responses":[],"searchText":"virtual mage","startedAt":"2025-07-10T15:36:05.6834182Z","state":"InProgress","token":137}...
2025-07-10 08:36:05 - newmusic.soulseek_client - INFO - search:267 - Search initiated with ID: 98191a41-4791-4d60-a8dc-29016f6a8f2f
2025-07-10 08:36:05 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 1/20) - elapsed: 0.0s
2025-07-10 08:36:05 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/98191a41-4791-4d60-a8dc-29016f6a8f2f/responses
2025-07-10 08:36:05 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:36:06 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:36:06 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:36:07 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 2/20) - elapsed: 1.5s
2025-07-10 08:36:07 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/98191a41-4791-4d60-a8dc-29016f6a8f2f/responses
2025-07-10 08:36:07 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:36:07 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:36:07 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:36:09 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Broque Main
2025-07-10 08:36:09 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 3/20) - elapsed: 3.0s
2025-07-10 08:36:09 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/98191a41-4791-4d60-a8dc-29016f6a8f2f/responses
2025-07-10 08:36:09 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:36:09 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:36:09 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:36:11 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 4/20) - elapsed: 4.5s
2025-07-10 08:36:11 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/98191a41-4791-4d60-a8dc-29016f6a8f2f/responses
2025-07-10 08:36:11 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:36:11 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:36:11 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:36:13 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 5/20) - elapsed: 6.0s
2025-07-10 08:36:13 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/98191a41-4791-4d60-a8dc-29016f6a8f2f/responses
2025-07-10 08:36:13 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:36:13 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:36:13 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:36:14 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 6/20) - elapsed: 7.5s
2025-07-10 08:36:14 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/98191a41-4791-4d60-a8dc-29016f6a8f2f/responses
2025-07-10 08:36:14 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:36:15 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:36:15 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:36:16 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 7/20) - elapsed: 9.0s
2025-07-10 08:36:16 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/98191a41-4791-4d60-a8dc-29016f6a8f2f/responses
2025-07-10 08:36:16 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:36:17 - newmusic.spotify_client - INFO - get_user_playlists:113 - Retrieved 7 playlists
2025-07-10 08:36:17 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:36:17 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:36:18 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 8/20) - elapsed: 10.5s
2025-07-10 08:36:18 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/98191a41-4791-4d60-a8dc-29016f6a8f2f/responses
2025-07-10 08:36:18 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:36:18 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:36:18 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:36:20 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 9/20) - elapsed: 12.0s
2025-07-10 08:36:20 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/98191a41-4791-4d60-a8dc-29016f6a8f2f/responses
2025-07-10 08:36:20 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:36:20 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:36:20 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:36:22 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 10/20) - elapsed: 13.5s
2025-07-10 08:36:22 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/98191a41-4791-4d60-a8dc-29016f6a8f2f/responses
2025-07-10 08:36:22 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:36:22 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:36:22 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 11/20) - elapsed: 15.0s
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/98191a41-4791-4d60-a8dc-29016f6a8f2f/responses
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: [{"fileCount":1,"files":[{"bitRate":128,"code":1,"extension":"","filename":"Shared\\Virtual Mage\\Refraction 1191940288\\Virtual Mage - Virtual Mage.mp3","isVariableBitRate":false,"length":154,"size":2476160,"isLocked":false}],"hasFreeUploadSlot":true,"lockedFileCount":0,"lockedFiles":[],"queueLength":9,"token":137,"uploadSpeed":518299,"username":"KindaRatchet"},{"fileCount":2,"files":[{"bitRate":160,"code":1,"extension":"","filename":"Music\\aofd3\\Mallsoft & Shopping Mall Ambience\\03 - Virtua...
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:208 - Processing 24 user responses
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User KindaRatchet has 1 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User zeroscan has 2 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User buk4 has 1 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User HomotopicCensorship has 3 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User exsanguinidraculae has 2 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User Letham has 3 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User belthane has 3 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User RuuqoHoosk has 346 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User aajzugikamlzcr has 1 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User leonardoclk has 3 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User Dax_VR has 10 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User Satani-Vitales has 3 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User ruszok has 1 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User FixxAlish has 1 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User wonko1987 has 2 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User thinkibettergo has 1 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User soulshookt has 3 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User ofoijacussa has 0 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User koalabeer has 3 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User raspberry2 has 10 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User nightmare_dronemk12 has 3 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User Ultravod has 9 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User Kaya-Sem has 1 files
2025-07-10 08:36:24 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User Shooby has 6 files
2025-07-10 08:36:24 - newmusic.soulseek_client - INFO - search:288 - Found 418 new results (total: 418) at 15.0s
2025-07-10 08:36:24 - newmusic.soulseek_client - INFO - search:298 - Early termination: Found 418 results, stopping search
2025-07-10 08:36:24 - newmusic.soulseek_client - INFO - search:309 - Search completed. Found 418 total results for query: virtual mage
2025-07-10 08:57:35 - newmusic.main - INFO - closeEvent:152 - Closing application...
2025-07-10 08:57:35 - newmusic.main - INFO - closeEvent:157 - Cleaning up Downloads page threads...
2025-07-10 08:57:35 - newmusic.main - INFO - closeEvent:162 - Stopping status monitoring thread...
2025-07-10 08:57:35 - newmusic.main - INFO - closeEvent:167 - Closing Soulseek client...
2025-07-10 08:57:35 - newmusic.main - INFO - closeEvent:173 - Application closed successfully
2025-07-10 08:57:40 - newmusic - INFO - setup_logging:57 - Logging initialized with level: DEBUG
2025-07-10 08:57:40 - newmusic.main - INFO - main:187 - Starting NewMusic application
2025-07-10 08:57:40 - newmusic.spotify_client - INFO - _setup_client:84 - Successfully authenticated with Spotify as broquethomas
2025-07-10 08:57:40 - newmusic.plex_client - INFO - _find_music_library:98 - Found music library: Music
2025-07-10 08:57:40 - newmusic.plex_client - INFO - _setup_client:84 - Successfully connected to Plex server: PLEX-MACHINE
2025-07-10 08:57:40 - newmusic.soulseek_client - INFO - _setup_client:87 - Soulseek client configured with slskd at http://localhost:5030
2025-07-10 08:57:41 - newmusic.main - INFO - change_page:139 - Changed to page: dashboard
2025-07-10 08:57:41 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Aether
2025-07-10 08:57:42 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Favorite Artists
2025-07-10 08:57:45 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Brittnea
2025-07-10 08:57:46 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Baleigh
2025-07-10 08:57:48 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Extra Music
2025-07-10 08:57:49 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Maggi Main
2025-07-10 08:57:55 - newmusic.spotify_client - INFO - get_user_playlists:106 - Fetching tracks for playlist: Broque Main
2025-07-10 08:58:03 - newmusic.spotify_client - INFO - get_user_playlists:113 - Retrieved 7 playlists
2025-07-10 08:58:04 - newmusic.main - INFO - change_page:139 - Changed to page: downloads
2025-07-10 08:58:07 - newmusic.soulseek_client - INFO - search:243 - Starting search for: 'virtual mage'
2025-07-10 08:58:07 - newmusic.soulseek_client - DEBUG - search:253 - Search data: {'searchText': 'virtual mage', 'timeout': 30000, 'filterResponses': True, 'minimumResponseFileCount': 1, 'minimumPeerUploadSpeed': 0}
2025-07-10 08:58:07 - newmusic.soulseek_client - DEBUG - search:254 - Making POST request to: http://localhost:5030/api/v0/searches
2025-07-10 08:58:07 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making POST request to: http://localhost:5030/api/v0/searches
2025-07-10 08:58:07 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:58:07 - newmusic.soulseek_client - DEBUG - _make_request:112 - JSON payload: {'searchText': 'virtual mage', 'timeout': 30000, 'filterResponses': True, 'minimumResponseFileCount': 1, 'minimumPeerUploadSpeed': 0}
2025-07-10 08:58:07 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:58:07 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: {"fileCount":0,"id":"809820d8-2d3b-4cee-9dc2-7b7a2398cfbd","isComplete":false,"lockedFileCount":0,"responseCount":0,"responses":[],"searchText":"virtual mage","startedAt":"2025-07-10T15:58:07.8291392Z","state":"InProgress","token":138}...
2025-07-10 08:58:07 - newmusic.soulseek_client - INFO - search:267 - Search initiated with ID: 809820d8-2d3b-4cee-9dc2-7b7a2398cfbd
2025-07-10 08:58:07 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 1/20) - elapsed: 0.0s
2025-07-10 08:58:07 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/809820d8-2d3b-4cee-9dc2-7b7a2398cfbd/responses
2025-07-10 08:58:07 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:58:08 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:58:08 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:58:09 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 2/20) - elapsed: 1.5s
2025-07-10 08:58:09 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/809820d8-2d3b-4cee-9dc2-7b7a2398cfbd/responses
2025-07-10 08:58:09 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:58:10 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:58:10 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:58:11 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 3/20) - elapsed: 3.0s
2025-07-10 08:58:11 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/809820d8-2d3b-4cee-9dc2-7b7a2398cfbd/responses
2025-07-10 08:58:11 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:58:11 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:58:11 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:58:13 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 4/20) - elapsed: 4.5s
2025-07-10 08:58:13 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/809820d8-2d3b-4cee-9dc2-7b7a2398cfbd/responses
2025-07-10 08:58:13 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:58:13 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:58:13 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:58:15 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 5/20) - elapsed: 6.0s
2025-07-10 08:58:15 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/809820d8-2d3b-4cee-9dc2-7b7a2398cfbd/responses
2025-07-10 08:58:15 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:58:15 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:58:15 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:58:17 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 6/20) - elapsed: 7.5s
2025-07-10 08:58:17 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/809820d8-2d3b-4cee-9dc2-7b7a2398cfbd/responses
2025-07-10 08:58:17 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:58:17 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:58:17 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:58:18 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 7/20) - elapsed: 9.0s
2025-07-10 08:58:18 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/809820d8-2d3b-4cee-9dc2-7b7a2398cfbd/responses
2025-07-10 08:58:18 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:58:19 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:58:19 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:58:20 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 8/20) - elapsed: 10.5s
2025-07-10 08:58:20 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/809820d8-2d3b-4cee-9dc2-7b7a2398cfbd/responses
2025-07-10 08:58:20 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:58:20 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:58:20 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:58:22 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 9/20) - elapsed: 12.0s
2025-07-10 08:58:22 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/809820d8-2d3b-4cee-9dc2-7b7a2398cfbd/responses
2025-07-10 08:58:22 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:58:22 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:58:22 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:58:24 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 10/20) - elapsed: 13.5s
2025-07-10 08:58:24 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/809820d8-2d3b-4cee-9dc2-7b7a2398cfbd/responses
2025-07-10 08:58:24 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:58:24 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:58:24 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:58:26 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 11/20) - elapsed: 15.0s
2025-07-10 08:58:26 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/809820d8-2d3b-4cee-9dc2-7b7a2398cfbd/responses
2025-07-10 08:58:26 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:58:26 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:58:26 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: []...
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - search:275 - Polling for results (attempt 12/20) - elapsed: 16.5s
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _make_request:109 - Making GET request to: http://localhost:5030/api/v0/searches/809820d8-2d3b-4cee-9dc2-7b7a2398cfbd/responses
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _make_request:110 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'}
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _make_request:121 - Response status: 200
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _make_request:122 - Response text: [{"fileCount":3,"files":[{"bitRate":183,"code":1,"extension":"","filename":"Music\\Vaporwave\\Vapor Vault\\virtual-internet-prison-vhs-wkvbyn\\BOSEBY - Virtual Internet Prison (VHS) - 02 Temple Vision.vhs Featuring Wave Mage.mp3","isVariableBitRate":true,"length":186,"size":4348704,"isLocked":false},{"bitRate":172,"code":1,"extension":"","filename":"Music\\Vaporwave\\Vapor Vault\\virtual-internet-prison-vhs-wkvbyn\\BOSEBY - Virtual Internet Prison (VHS) - 05 SD.vhs Featuring PKSkyler, CentaurSap...
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:208 - Processing 23 user responses
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User koalabeer has 3 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User Letham has 3 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User raspberry2 has 10 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User Kaya-Sem has 1 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User Satani-Vitales has 3 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User aajzugikamlzcr has 1 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User zeroscan has 2 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User RuuqoHoosk has 346 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User buk4 has 1 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User Ultravod has 9 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User HomotopicCensorship has 3 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User belthane has 3 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User wonko1987 has 2 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User exsanguinidraculae has 2 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User Shooby has 6 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User leonardoclk has 3 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User soulshookt has 3 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User FixxAlish has 1 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User thinkibettergo has 1 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User ruszok has 1 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User ofoijacussa has 0 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User KindaRatchet has 1 files
2025-07-10 08:58:28 - newmusic.soulseek_client - DEBUG - _process_search_responses:213 - User nightmare_dronemk12 has 3 files
2025-07-10 08:58:28 - newmusic.soulseek_client - INFO - search:288 - Found 408 new results (total: 408) at 16.5s
2025-07-10 08:58:28 - newmusic.soulseek_client - INFO - search:298 - Early termination: Found 408 results, stopping search
2025-07-10 08:58:28 - newmusic.soulseek_client - INFO - search:309 - Search completed. Found 408 total results for query: virtual mage

View file

@ -243,15 +243,27 @@ class SearchThread(QThread):
class SearchResultItem(QFrame):
download_requested = pyqtSignal(object) # SearchResult object
expansion_requested = pyqtSignal(object) # Signal when this item wants to expand
def __init__(self, search_result, parent=None):
super().__init__(parent)
self.search_result = search_result
self.is_downloading = False
self.is_expanded = False
self.setup_ui()
def setup_ui(self):
self.setFixedHeight(90) # Professional height to prevent overflow
# Dynamic height based on state (compact: 60px, expanded: 180px for better content fit)
self.compact_height = 60
self.expanded_height = 180 # Increased from 140px to fit content properly
self.setFixedHeight(self.compact_height)
# Ensure consistent sizing and layout behavior
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
# Enable mouse tracking for click detection
self.setMouseTracking(True)
self.setStyleSheet("""
SearchResultItem {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
@ -259,122 +271,53 @@ class SearchResultItem(QFrame):
stop:1 rgba(32, 32, 32, 0.95));
border-radius: 12px;
border: 1px solid rgba(64, 64, 64, 0.4);
margin: 6px 4px;
margin: 4px 2px;
}
SearchResultItem:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 rgba(50, 50, 50, 0.95),
stop:1 rgba(40, 40, 40, 0.98));
border: 1px solid rgba(29, 185, 84, 0.7);
cursor: pointer;
}
""")
layout = QHBoxLayout(self)
layout.setContentsMargins(16, 12, 16, 12) # Professional padding
layout.setSpacing(16) # Consistent spacing
layout.setContentsMargins(12, 8, 12, 8) # Tighter padding for compact view
layout.setSpacing(12)
# Compact album art
album_art = QLabel()
album_art.setFixedSize(56, 56)
album_art.setStyleSheet("""
# Left section: Music icon + filename
left_section = QHBoxLayout()
left_section.setSpacing(8)
# Compact music icon
music_icon = QLabel("🎵")
music_icon.setFixedSize(32, 32)
music_icon.setAlignment(Qt.AlignmentFlag.AlignCenter)
music_icon.setStyleSheet("""
QLabel {
background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
stop:0 rgba(29, 185, 84, 0.25),
stop:1 rgba(29, 185, 84, 0.08));
border-radius: 6px;
border: 1px solid rgba(29, 185, 84, 0.3);
stop:0 rgba(29, 185, 84, 0.3),
stop:1 rgba(29, 185, 84, 0.1));
border-radius: 16px;
border: 1px solid rgba(29, 185, 84, 0.4);
font-size: 14px;
}
""")
album_art.setAlignment(Qt.AlignmentFlag.AlignCenter)
album_art.setText("🎵")
album_art.setFont(QFont("Arial", 18))
# Efficient content layout - single column
content_layout = QVBoxLayout()
content_layout.setSpacing(2)
content_layout.setContentsMargins(0, 0, 0, 0)
# Content area that will change based on expanded state
self.content_widget = QWidget()
self.content_layout = QVBoxLayout(self.content_widget)
self.content_layout.setContentsMargins(0, 0, 0, 0)
self.content_layout.setSpacing(3) # Tighter spacing for better content density
# Extract song info
primary_info = self._extract_song_info()
# Top row: Song title + quality badge
top_row = QHBoxLayout()
top_row.setSpacing(8)
# Create both compact and expanded content but show only one
self.create_persistent_content(primary_info)
song_title = QLabel(primary_info['title'])
song_title.setFont(QFont("Arial", 12, QFont.Weight.Bold))
song_title.setStyleSheet("color: #ffffff;")
song_title.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
quality_badge = self._create_compact_quality_badge()
top_row.addWidget(song_title)
top_row.addWidget(quality_badge)
# Middle row: Artist + file size + duration
middle_row = QHBoxLayout()
middle_row.setSpacing(12)
artist_info = QLabel(primary_info['artist'])
artist_info.setFont(QFont("Arial", 10, QFont.Weight.Normal))
artist_info.setStyleSheet("color: rgba(179, 179, 179, 0.9);")
artist_info.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
# File details (compact)
details = []
size_mb = self.search_result.size // (1024*1024)
details.append(f"{size_mb}MB")
if self.search_result.duration:
duration_mins = self.search_result.duration // 60
duration_secs = self.search_result.duration % 60
details.append(f"{duration_mins}:{duration_secs:02d}")
file_details = QLabel("".join(details))
file_details.setFont(QFont("Arial", 9))
file_details.setStyleSheet("color: rgba(136, 136, 136, 0.8);")
middle_row.addWidget(artist_info)
middle_row.addWidget(file_details)
# Bottom row: User info + speed indicator
bottom_row = QHBoxLayout()
bottom_row.setSpacing(8)
user_info = QLabel(f"👤 {self.search_result.username}")
user_info.setFont(QFont("Arial", 9, QFont.Weight.Medium))
user_info.setStyleSheet("color: rgba(29, 185, 84, 0.8);")
speed_indicator = self._create_compact_speed_indicator()
bottom_row.addWidget(user_info)
bottom_row.addWidget(speed_indicator)
bottom_row.addStretch()
content_layout.addLayout(top_row)
content_layout.addLayout(middle_row)
content_layout.addLayout(bottom_row)
# Compact action area
action_layout = QVBoxLayout()
action_layout.setSpacing(4)
action_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
action_layout.setContentsMargins(0, 0, 0, 0)
# Compact quality score
quality_score = QLabel(f"{self.search_result.quality_score:.1f}")
quality_score.setAlignment(Qt.AlignmentFlag.AlignCenter)
quality_score.setFont(QFont("Arial", 9, QFont.Weight.Bold))
quality_score.setFixedSize(36, 18)
if self.search_result.quality_score >= 0.9:
quality_score.setStyleSheet("color: #1db954; background: rgba(29, 185, 84, 0.15); border-radius: 9px;")
elif self.search_result.quality_score >= 0.7:
quality_score.setStyleSheet("color: #ffa500; background: rgba(255, 165, 0, 0.15); border-radius: 9px;")
else:
quality_score.setStyleSheet("color: #e22134; background: rgba(226, 33, 52, 0.15); border-radius: 9px;")
# Compact download button
# Right section: Always-visible download button
self.download_btn = QPushButton("⬇️")
self.download_btn.setFixedSize(36, 36)
self.download_btn.clicked.connect(self.request_download)
@ -401,12 +344,197 @@ class SearchResultItem(QFrame):
}
""")
action_layout.addWidget(quality_score)
action_layout.addWidget(self.download_btn)
# Assemble the layout
left_section.addWidget(music_icon)
left_section.addWidget(self.content_widget, 1)
layout.addWidget(album_art)
layout.addLayout(content_layout, 1) # Take most space
layout.addLayout(action_layout)
layout.addLayout(left_section, 1)
layout.addWidget(self.download_btn)
def create_persistent_content(self, primary_info):
"""Create both compact and expanded content with visibility control"""
# Title row (always visible) with character limit and ellipsis
title_text = primary_info['title']
if len(title_text) > 50: # Character limit for long titles
title_text = title_text[:47] + "..."
self.title_label = QLabel(title_text)
self.title_label.setFont(QFont("Arial", 11, QFont.Weight.Bold)) # Reduced from 13px to 11px
self.title_label.setStyleSheet("color: #ffffff;")
self.title_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
# Ensure text doesn't overflow the label
self.title_label.setWordWrap(False)
self.title_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
# Expand indicator
self.expand_indicator = QLabel("")
self.expand_indicator.setFixedSize(16, 16)
self.expand_indicator.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.expand_indicator.setStyleSheet("color: rgba(255, 255, 255, 0.6); font-size: 12px;")
# Quality badge (only visible when expanded)
self.quality_badge = self._create_compact_quality_badge()
self.quality_badge.hide() # Initially hidden
title_row = QHBoxLayout()
title_row.setContentsMargins(0, 0, 0, 0)
title_row.addWidget(self.title_label)
title_row.addWidget(self.quality_badge)
title_row.addWidget(self.expand_indicator)
# Expanded content (initially hidden)
self.expanded_content = QWidget()
expanded_layout = QVBoxLayout(self.expanded_content)
expanded_layout.setContentsMargins(0, 0, 0, 0)
expanded_layout.setSpacing(2) # Very tight spacing for dense layout
# Artist info
self.artist_info = QLabel(primary_info['artist'])
self.artist_info.setFont(QFont("Arial", 10, QFont.Weight.Normal)) # Slightly smaller font
self.artist_info.setStyleSheet("color: rgba(179, 179, 179, 0.9);")
# File details
details = []
size_mb = self.search_result.size // (1024*1024)
details.append(f"{size_mb}MB")
if self.search_result.duration:
duration_mins = self.search_result.duration // 60
duration_secs = self.search_result.duration % 60
details.append(f"{duration_mins}:{duration_secs:02d}")
self.file_details = QLabel("".join(details))
self.file_details.setFont(QFont("Arial", 9)) # Smaller font for compactness
self.file_details.setStyleSheet("color: rgba(136, 136, 136, 0.8);")
# User info and quality score in one compact row
bottom_row = QHBoxLayout()
bottom_row.setContentsMargins(0, 0, 0, 0)
bottom_row.setSpacing(8)
self.user_info = QLabel(f"👤 {self.search_result.username}")
self.user_info.setFont(QFont("Arial", 9, QFont.Weight.Medium)) # Smaller font
self.user_info.setStyleSheet("color: rgba(29, 185, 84, 0.8);")
self.speed_indicator = self._create_compact_speed_indicator()
self.quality_score = QLabel(f"{self.search_result.quality_score:.1f}")
self.quality_score.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.quality_score.setFont(QFont("Arial", 8, QFont.Weight.Bold)) # Smaller font
self.quality_score.setFixedSize(32, 16) # Smaller size
if self.search_result.quality_score >= 0.9:
self.quality_score.setStyleSheet("color: #1db954; background: rgba(29, 185, 84, 0.15); border-radius: 8px;")
elif self.search_result.quality_score >= 0.7:
self.quality_score.setStyleSheet("color: #ffa500; background: rgba(255, 165, 0, 0.15); border-radius: 8px;")
else:
self.quality_score.setStyleSheet("color: #e22134; background: rgba(226, 33, 52, 0.15); border-radius: 8px;")
bottom_row.addWidget(self.user_info)
bottom_row.addWidget(self.speed_indicator)
bottom_row.addStretch()
bottom_row.addWidget(self.quality_score)
# Add all expanded content
expanded_layout.addWidget(self.artist_info)
expanded_layout.addWidget(self.file_details)
expanded_layout.addLayout(bottom_row)
# Initially hide expanded content
self.expanded_content.hide()
# Add to main layout
self.content_layout.addLayout(title_row)
self.content_layout.addWidget(self.expanded_content)
def update_expanded_state(self):
"""Update UI based on expanded state without recreating widgets"""
if self.is_expanded:
self.expand_indicator.setText("")
self.quality_badge.show()
self.expanded_content.show()
else:
self.expand_indicator.setText("")
self.quality_badge.hide()
self.expanded_content.hide()
def mousePressEvent(self, event):
"""Handle mouse clicks to toggle expand/collapse"""
# Only respond to left clicks and avoid clicks on the download button
if event.button() == Qt.MouseButton.LeftButton:
# Check if click is on download button (more precise detection)
button_rect = self.download_btn.geometry()
# Add some padding to the button area to be more forgiving
button_rect.adjust(-5, -5, 5, 5)
if not button_rect.contains(event.pos()):
# Emit signal to parent to handle accordion behavior
self.expansion_requested.emit(self)
super().mousePressEvent(event)
def set_expanded(self, expanded, animate=True):
"""Set expanded state externally (called by parent for accordion behavior)"""
if self.is_expanded == expanded:
return # No change needed
self.is_expanded = expanded
if animate:
self._animate_to_state()
else:
# Immediate state change without animation
if self.is_expanded:
self.setFixedHeight(self.expanded_height)
else:
self.setFixedHeight(self.compact_height)
self.update_expanded_state()
def toggle_expanded(self):
"""Toggle between compact and expanded states with animation"""
self.set_expanded(not self.is_expanded, animate=True)
def _animate_to_state(self):
"""Animate to the current expanded state"""
from PyQt6.QtCore import QPropertyAnimation, QEasingCurve
# Start height animation first
self.animation = QPropertyAnimation(self, b"minimumHeight")
self.animation.setDuration(200) # Slightly faster animation for better responsiveness
self.animation.setEasingCurve(QEasingCurve.Type.OutCubic)
if self.is_expanded:
# Expand animation
self.animation.setStartValue(self.compact_height)
self.animation.setEndValue(self.expanded_height)
# Show content immediately for expand (feels more responsive)
self.update_expanded_state()
else:
# Collapse animation
self.animation.setStartValue(self.expanded_height)
self.animation.setEndValue(self.compact_height)
# Hide content immediately for collapse (cleaner look)
self.update_expanded_state()
# Update fixed height when animation completes
self.animation.finished.connect(self._finalize_height)
self.animation.start()
def _finalize_height(self):
"""Set final height after animation completes"""
if self.is_expanded:
self.setFixedHeight(self.expanded_height)
else:
self.setFixedHeight(self.compact_height)
# Force parent layout update to ensure proper spacing
if self.parent():
self.parent().updateGeometry()
def sizeHint(self):
"""Provide consistent size hint for layout calculations"""
if self.is_expanded:
return self.size().expandedTo(self.minimumSize()).boundedTo(self.maximumSize())
else:
return self.size().expandedTo(self.minimumSize()).boundedTo(self.maximumSize())
def _extract_song_info(self):
"""Extract song title and artist from filename"""
@ -800,25 +928,26 @@ class DownloadQueue(QFrame):
""")
layout = QVBoxLayout(self)
layout.setContentsMargins(16, 12, 16, 16) # Professional spacing - less top padding
layout.setSpacing(12) # Reduced spacing for tighter layout
layout.setContentsMargins(16, 8, 16, 16) # Reduced top padding
layout.setSpacing(8) # Tighter spacing for more compact layout
# Header
header_layout = QHBoxLayout()
header_layout.setContentsMargins(0, 0, 0, 0)
title_label = QLabel("Download Queue")
title_label.setFont(QFont("Segoe UI", 14, QFont.Weight.Bold))
title_label.setStyleSheet("""
color: rgba(255, 255, 255, 0.95);
font-weight: 600;
padding: 4px 0;
padding: 0;
""")
queue_count = QLabel("Empty")
queue_count.setFont(QFont("Segoe UI", 10))
queue_count.setStyleSheet("""
color: rgba(255, 255, 255, 0.6);
padding: 4px 0;
padding: 0;
""")
header_layout.addWidget(title_label)
@ -926,15 +1055,15 @@ class DownloadsPage(QWidget):
self.search_results = []
self.download_items = [] # Track download items for the queue
self.displayed_results = 0 # Track how many results are currently displayed
self.results_per_page = 20 # Show 20 results at a time
self.results_per_page = 15 # Show 15 results at a time
self.is_loading_more = False # Prevent multiple simultaneous loads
self.currently_expanded_item = None # Track which item is currently expanded
# Download status polling timer
self.download_status_timer = QTimer()
self.download_status_timer.timeout.connect(self.update_download_status)
self.download_status_timer.start(2000) # Poll every 2 seconds
# Initialize load more button (will be added to layout in setup_ui)
self.load_more_btn = None
self.setup_ui()
@ -948,26 +1077,35 @@ class DownloadsPage(QWidget):
""")
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(24, 16, 24, 20) # Professional spacing using 8px grid
main_layout.setSpacing(16)
# Responsive margins that adapt to window size
main_layout.setContentsMargins(16, 12, 16, 16) # Reduced for tighter responsive feel
main_layout.setSpacing(12) # Consistent 12px spacing
# Elegant Header
header = self.create_elegant_header()
main_layout.addWidget(header)
# Main Content Area - Prioritize Search Results
content_splitter = QHBoxLayout()
content_splitter.setSpacing(16)
# Main Content Area with responsive splitter
content_splitter = QSplitter(Qt.Orientation.Horizontal)
content_splitter.setChildrenCollapsible(False) # Prevent panels from collapsing completely
# LEFT: Search & Results (70% of space)
# LEFT: Search & Results section
search_and_results = self.create_search_and_results_section()
content_splitter.addWidget(search_and_results, 7)
search_and_results.setMinimumWidth(400) # Minimum width for usability
content_splitter.addWidget(search_and_results)
# RIGHT: Collapsible Controls Panel (30% of space)
# RIGHT: Controls Panel
controls_panel = self.create_collapsible_controls_panel()
content_splitter.addWidget(controls_panel, 3)
controls_panel.setMinimumWidth(280) # Minimum width for controls
controls_panel.setMaximumWidth(400) # Maximum width to prevent overgrowth
content_splitter.addWidget(controls_panel)
main_layout.addLayout(content_splitter)
# Set initial splitter proportions (roughly 70/30)
content_splitter.setSizes([700, 300])
content_splitter.setStretchFactor(0, 1) # Search results gets priority for extra space
content_splitter.setStretchFactor(1, 0) # Controls panel stays fixed width when possible
main_layout.addWidget(content_splitter)
# Optional: Compact status bar at bottom
status_bar = self.create_compact_status_bar()
@ -976,7 +1114,9 @@ class DownloadsPage(QWidget):
def create_elegant_header(self):
"""Create an elegant, minimal header"""
header = QFrame()
header.setFixedHeight(100) # Increased height to prevent text clipping
header.setMinimumHeight(80) # Minimum height, can grow if needed
header.setMaximumHeight(120) # Maximum to prevent overgrowth
header.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
header.setStyleSheet("""
QFrame {
background: transparent;
@ -985,8 +1125,8 @@ class DownloadsPage(QWidget):
""")
layout = QHBoxLayout(header)
layout.setContentsMargins(24, 20, 24, 20) # More balanced padding
layout.setSpacing(16)
layout.setContentsMargins(16, 12, 16, 12) # Responsive padding consistent with main layout
layout.setSpacing(12) # Consistent spacing
# Icon and Title
title_section = QVBoxLayout()
@ -1031,8 +1171,8 @@ class DownloadsPage(QWidget):
""")
layout = QVBoxLayout(section)
layout.setContentsMargins(24, 20, 24, 20) # Professional spacing consistent with main layout
layout.setSpacing(16)
layout.setContentsMargins(16, 12, 16, 12) # Responsive spacing consistent with main layout
layout.setSpacing(12) # Consistent 12px spacing
# Elegant Search Bar
search_container = self.create_elegant_search_bar()
@ -1063,8 +1203,8 @@ class DownloadsPage(QWidget):
""")
results_layout = QVBoxLayout(results_container)
results_layout.setContentsMargins(16, 12, 16, 16) # Consistent with card padding
results_layout.setSpacing(8)
results_layout.setContentsMargins(12, 8, 12, 12) # Tighter responsive spacing
results_layout.setSpacing(8) # Consistent small spacing for tight layout
# Results header
results_header = QLabel("Search Results")
@ -1112,6 +1252,10 @@ class DownloadsPage(QWidget):
self.search_results_layout.addStretch()
self.search_results_scroll.setWidget(self.search_results_widget)
# Connect scroll detection for automatic loading
scroll_bar = self.search_results_scroll.verticalScrollBar()
scroll_bar.valueChanged.connect(self.on_scroll_changed)
results_layout.addWidget(self.search_results_scroll)
layout.addWidget(results_container, 1) # This takes all remaining space
@ -1132,13 +1276,14 @@ class DownloadsPage(QWidget):
""")
layout = QHBoxLayout(container)
layout.setContentsMargins(24, 16, 24, 16) # Professional spacing using 8px grid
layout.setSpacing(12)
layout.setContentsMargins(16, 12, 16, 12) # Consistent responsive spacing
layout.setSpacing(12) # Consistent spacing throughout
# Search input with enhanced styling
self.search_input = QLineEdit()
self.search_input.setPlaceholderText("Search for music... (e.g., 'Virtual Mage', 'Queen Bohemian Rhapsody')")
self.search_input.setFixedHeight(40)
self.search_input.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) # Responsive width
self.search_input.returnPressed.connect(self.perform_search)
self.search_input.setStyleSheet("""
QLineEdit {
@ -1209,8 +1354,8 @@ class DownloadsPage(QWidget):
""")
layout = QVBoxLayout(panel)
layout.setContentsMargins(16, 16, 16, 16)
layout.setSpacing(12)
layout.setContentsMargins(12, 12, 12, 12) # Consistent responsive spacing
layout.setSpacing(12) # Consistent spacing throughout
# Panel header
header = QLabel("Download Manager")
@ -1418,31 +1563,7 @@ class DownloadsPage(QWidget):
self.search_results_layout = QVBoxLayout(self.search_results_widget)
self.search_results_layout.setSpacing(5)
# Create Load More button and add to search results layout
self.load_more_btn = QPushButton("📚 Load More Results")
self.load_more_btn.setFixedHeight(40)
self.load_more_btn.clicked.connect(self.show_more_results)
self.load_more_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 rgba(29, 185, 84, 0.8),
stop:1 rgba(25, 160, 75, 0.9));
border: none;
border-radius: 20px;
color: #000000;
font-size: 12px;
font-weight: bold;
margin: 8px;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 rgba(30, 215, 96, 0.9),
stop:1 rgba(25, 180, 80, 1.0));
}
""")
self.load_more_btn.hide() # Initially hidden
self.search_results_layout.addWidget(self.load_more_btn)
# Just add stretch - no load more button needed with auto-scroll
self.search_results_layout.addStretch()
self.search_results_scroll.setWidget(self.search_results_widget)
@ -1473,8 +1594,8 @@ class DownloadsPage(QWidget):
# Clear previous results and reset state
self.clear_search_results()
self.displayed_results = 0
if self.load_more_btn:
self.load_more_btn.hide()
self.is_loading_more = False
self.currently_expanded_item = None # Reset expanded state
# Enhanced searching state with animation
self.search_btn.setText("🔍 Searching...")
@ -1536,70 +1657,79 @@ class DownloadsPage(QWidget):
def on_search_results_partial(self, new_results, total_count):
"""Handle progressive search results as they come in"""
# Sort new results by quality score
# Sort new results by quality score and add to master list
new_results.sort(key=lambda x: x.quality_score, reverse=True)
# Add new result items to UI immediately
for result in new_results:
result_item = SearchResultItem(result)
result_item.download_requested.connect(self.start_download)
# Insert before the Load More button (which is before the stretch)
insert_position = self.search_results_layout.count() - 2
self.search_results_layout.insertWidget(insert_position, result_item)
# Add to master search results list (don't display all immediately)
if not hasattr(self, '_temp_search_results'):
self._temp_search_results = []
# Update displayed count
self.displayed_results += len(new_results)
self._temp_search_results.extend(new_results)
# Only display up to the current page limit
remaining_slots = self.results_per_page - self.displayed_results
if remaining_slots > 0:
results_to_show = new_results[:remaining_slots]
# Temporarily disable layout updates for smoother batch loading
self.search_results_widget.setUpdatesEnabled(False)
for result in results_to_show:
result_item = SearchResultItem(result)
result_item.download_requested.connect(self.start_download)
result_item.expansion_requested.connect(self.handle_expansion_request)
# Insert before the stretch
insert_position = self.search_results_layout.count() - 1
self.search_results_layout.insertWidget(insert_position, result_item)
# Re-enable updates and force layout refresh
self.search_results_widget.setUpdatesEnabled(True)
self.search_results_widget.updateGeometry()
self.search_results_layout.update()
self.search_results_scroll.updateGeometry()
self.displayed_results += len(results_to_show)
# Update status message with real-time feedback
self.update_search_status(f"✨ Found {total_count} results so far • Still searching...", "#1db954")
# If we have enough results, show the Load More button
if total_count >= self.results_per_page and self.load_more_btn:
# Don't show Load More yet during live search - wait for completion
pass
if self.displayed_results < self.results_per_page:
self.update_search_status(f"✨ Found {total_count} results so far • Showing first {self.displayed_results}", "#1db954")
else:
self.update_search_status(f"✨ Found {total_count} results so far • Showing first {self.results_per_page} (scroll for more)", "#1db954")
def on_search_completed(self, results):
self.search_btn.setText("🔍 Search")
self.search_btn.setEnabled(True)
if not results:
# Only show no results if we haven't already displayed some via progressive loading
# Use temp results from progressive loading if available, otherwise use results
if hasattr(self, '_temp_search_results') and self._temp_search_results:
self.search_results = self._temp_search_results
del self._temp_search_results # Clean up temp storage
else:
self.search_results = results or []
total_results = len(self.search_results)
if total_results == 0:
if self.displayed_results == 0:
self.update_search_status("😔 No results found • Try a different search term or artist name", "#ffa500")
else:
# Search completed with some results already shown
self.update_search_status(f"✨ Search completed • Found {self.displayed_results} total results", "#1db954")
return
# Store all results for pagination
self.search_results = results
# If we already have results from progressive loading, just finalize the status
if self.displayed_results > 0:
total_results = len(results)
# Update status based on whether there are more results to load
if self.displayed_results < total_results:
remaining = total_results - self.displayed_results
if remaining > 0:
# Show Load More button for remaining results
self.update_search_status(f"✨ Showing {self.displayed_results} of {total_results} results • {remaining} more available", "#1db954")
if self.load_more_btn:
self.load_more_btn.show()
self.load_more_btn.setText(f"📚 Load {min(remaining, self.results_per_page)} More Results")
else:
# All results already displayed
self.update_search_status(f"✨ Showing all {total_results} results", "#1db954")
if self.load_more_btn:
self.load_more_btn.hide()
self.update_search_status(f"✨ Found {total_results} results • Showing first {self.displayed_results} (scroll down for {remaining} more)", "#1db954")
else:
# Fallback: No progressive results, show first page normally
self.search_results.sort(key=lambda x: x.quality_score, reverse=True)
self.clear_search_results()
self.displayed_results = 0
self.show_more_results()
self.update_search_status(f"✨ Search completed • Showing all {total_results} results", "#1db954")
# If we have no displayed results yet, show the first batch
if self.displayed_results == 0 and total_results > 0:
self.load_more_results()
def clear_search_results(self):
"""Clear all search result items from the layout"""
# Remove all SearchResultItem widgets (but keep Load More button and stretch)
# Remove all SearchResultItem widgets (but keep stretch)
items_to_remove = []
for i in range(self.search_results_layout.count()):
item = self.search_results_layout.itemAt(i)
@ -1610,60 +1740,80 @@ class DownloadsPage(QWidget):
self.search_results_layout.removeWidget(widget)
widget.deleteLater()
def show_more_results(self):
"""Show the next batch of search results"""
if not self.search_results:
def on_scroll_changed(self, value):
"""Handle scroll changes to implement lazy loading"""
if self.is_loading_more or not self.search_results:
return
scroll_bar = self.search_results_scroll.verticalScrollBar()
# Check if we're near the bottom (90% scrolled)
if scroll_bar.maximum() > 0:
scroll_percentage = value / scroll_bar.maximum()
if scroll_percentage >= 0.9 and self.displayed_results < len(self.search_results):
self.load_more_results()
def load_more_results(self):
"""Load the next batch of search results"""
if self.is_loading_more or not self.search_results:
return
self.is_loading_more = True
# Calculate how many more results to show
start_index = self.displayed_results
end_index = min(start_index + self.results_per_page, len(self.search_results))
# Only add results that haven't been displayed yet
# (Progressive loading may have already shown some results)
currently_displayed = self.count_displayed_results()
# Temporarily disable layout updates for smoother batch loading
self.search_results_widget.setUpdatesEnabled(False)
# Add result items to UI
for i in range(max(start_index, currently_displayed), end_index):
if i < len(self.search_results):
result = self.search_results[i]
result_item = SearchResultItem(result)
result_item.download_requested.connect(self.start_download)
# Insert before the Load More button (which is before the stretch)
insert_position = self.search_results_layout.count() - 2
self.search_results_layout.insertWidget(insert_position, result_item)
for i in range(start_index, end_index):
result = self.search_results[i]
result_item = SearchResultItem(result)
result_item.download_requested.connect(self.start_download)
result_item.expansion_requested.connect(self.handle_expansion_request)
# Insert before the stretch (which is always last)
insert_position = self.search_results_layout.count() - 1
self.search_results_layout.insertWidget(insert_position, result_item)
# Re-enable updates and force layout refresh
self.search_results_widget.setUpdatesEnabled(True)
self.search_results_widget.updateGeometry()
self.search_results_layout.update()
# Force scroll area to recognize new content size
self.search_results_scroll.updateGeometry()
# Update displayed count
self.displayed_results = end_index
# Update status and Load More button
self.update_load_more_status()
def count_displayed_results(self):
"""Count how many SearchResultItem widgets are currently displayed"""
count = 0
for i in range(self.search_results_layout.count()):
item = self.search_results_layout.itemAt(i)
if item and item.widget() and isinstance(item.widget(), SearchResultItem):
count += 1
return count
def update_load_more_status(self):
"""Update status message and Load More button based on current state"""
if not self.search_results:
return
# Update status
total_results = len(self.search_results)
if self.displayed_results >= total_results:
self.update_search_status(f"✨ Showing all {total_results} results by quality", "#1db954")
if self.load_more_btn:
self.load_more_btn.hide()
self.update_search_status(f"✨ Showing all {total_results} results", "#1db954")
else:
remaining = total_results - self.displayed_results
self.update_search_status(f"✨ Showing {self.displayed_results} of {total_results} results • {remaining} more available", "#1db954")
if self.load_more_btn:
self.load_more_btn.show()
self.load_more_btn.setText(f"📚 Load {min(remaining, self.results_per_page)} More Results")
self.update_search_status(f"✨ Showing {self.displayed_results} of {total_results} results (scroll for {remaining} more)", "#1db954")
self.is_loading_more = False
def handle_expansion_request(self, requesting_item):
"""Handle accordion-style expansion where only one item can be expanded at a time"""
# If there's a currently expanded item and it's not the requesting item, collapse it
if self.currently_expanded_item and self.currently_expanded_item != requesting_item:
self.currently_expanded_item.set_expanded(False, animate=True)
# Toggle the requesting item
new_expanded_state = not requesting_item.is_expanded
requesting_item.set_expanded(new_expanded_state, animate=True)
# Update tracking
if new_expanded_state:
self.currently_expanded_item = requesting_item
else:
self.currently_expanded_item = None
def on_search_failed(self, error_msg):
self.search_btn.setText("🔍 Search")