From f647df1ebee6a50302b22c4aa2a54fa47ec4d36b Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Thu, 10 Jul 2025 09:03:54 -0700 Subject: [PATCH] fix --- .../soulseek_client.cpython-312.pyc | Bin 32643 -> 32643 bytes core/soulseek_client.py | 2 +- logs/app.log | 345 ++++++++++ .../__pycache__/downloads.cpython-312.pyc | Bin 98823 -> 107399 bytes ui/pages/downloads.py | 624 +++++++++++------- 5 files changed, 733 insertions(+), 238 deletions(-) diff --git a/core/__pycache__/soulseek_client.cpython-312.pyc b/core/__pycache__/soulseek_client.cpython-312.pyc index 139aac2b8dc00497e583bee1f4c70daab35f9b4c..41e9183fe42564d7e36472bb1bad8b8309bd4f94 100644 GIT binary patch delta 367 zcmZqv&)EE*k@qw&FBbz4ti73^adRVYq&3q^-OU-+5v(jlm!u6hA91keX57BX$AO)3 z_hiT5H;jii>xERYs2l-mDLM)w1V986h&To!j)RC3AmZfYGZCR|XF<$;o7E%#Fab?a ziLqfkwK*~7A~WN*$;Julss};RAR{!{i_}2ueh_gPNZevCN-ZwUDJd@6ytzJM2`l5R z%_?a=T#WN47Zyk{?wZ_F(8_plvQXi3h!y81|1VrDyc48*4~Wh%X@G8;HmQ5kEJV)VyG1TroMeZY3l0WcKtX7$KFOh6M< zVr&>sZcdE3$jrEPvT=gC>H&~6$OujLA~g`ZA4FUN61UikQj1G-N{Wj%ZLUvP!peAa zvr3u|7vsFig#}WKJ16%Pv@#x;EL8X$V#V3X{|grj?*QrE4I=gc2{%pVqNkI$7S%H@ zooreh$#xGU@oe&xVim@hlXn(NGw$4cwb+6kXe3{CE|`>3q;%j5uZWCR}hg0B7STxsd>T3xO{SK-AYEL$?Wwt0PNLw AbN~PV diff --git a/core/soulseek_client.py b/core/soulseek_client.py index 05a0b09b..b1f6ed44 100644 --- a/core/soulseek_client.py +++ b/core/soulseek_client.py @@ -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: diff --git a/logs/app.log b/logs/app.log index 3b53eea3..ed0e9ea5 100644 --- a/logs/app.log +++ b/logs/app.log @@ -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 diff --git a/ui/pages/__pycache__/downloads.cpython-312.pyc b/ui/pages/__pycache__/downloads.cpython-312.pyc index 1df5ab399e662bec3f7d599285156257534c3128..912efd495341fc736b2ce1a6a35291a73d59cddd 100644 GIT binary patch delta 21190 zcmb7s31C#!)$rVxS>9ycWShxk&rJ4A_CSDygoF?Xi)?|&4snthAR!xXCP30)Ky3}x z7VxSM7fMj9@l$JP)YvKlt$%ClmPf)jaViSdzZPvRAhs@T{m;2?CJ9TI|Dh-E-R0cv zoO91T=iIsbF74gF)`q-cGzP2SdFn#j*6kaQhb);+eoQw^x6&P4SdTNJSGAcq$zA$& zIwyCRY8~$k0;qSY0S2=?&0YLDgVW^H-bL04P93Bhojkx0rygLaGZ86;8K6Gb%A*+OE{dw74gbWlqyG|6>C z(iy8s`XGVu^yqWZ^tobz{-V($?QbGR5=l1}N0BJ%_@l|BacZ6L|I_}l5LGv!7tTe} zQ*R2U$UqOC_P^}r=<=2*8eC$ai`s%ioVus|o!lMN9Yh^EbC{EN@=tS0aljQs2U;Ux zW=_nBr%#?RO9%ACK!Rw^ezWwCozRmEnsmYxWe&9C$qu}AtC}+DZ zkwXt^Oj2zkFe`t zHPQC3LdYz7^s7k8swbu}XXvDfAWaomM#Et$4f7;Q(}RgEvQuRbTY9k!I&$%O0JV-o zFn9E`>V&};=A{r#QTs02lm`k(;@~&c!G!k7>d=M`(cLD_w6(W2wt8G5J-8$~_M(6Y zkRUp68803A;R_dS@ZwlaZ42%tQz;ovp-=uQlq=mfxO!xz{Nw8JK<+~UJ;0rB)51+aFFE8!Cn2PvjKq{5@M$?MjCTjat!{G03d5)W6 z^My3DwX{1LJat=Lja#;Qe4zpA4V~a!VWR1CjfmJqq#1wcF@1$)ti9O%Kt2+{=a*yhKBIEhE zF>5mNe*73Oc=XsL%^pRdZQRWj5 zb&c{_Y+UTiZ3j59diZ;1_2$5pa~zqCIaFt z!f9|CCdnsNQz}YTdg+82gx@2&L?sZG&iY4fEykdLFg+Ea16f)ZEe-3T!m^Wz zoM#$6T@p!V(4$u)!;Mb(H%Tgk%rB3mZ%<|NlWhUzkG$^4)BHMCCLc z9;XG^0sTeEV(D}(F=-Y~Bw()%<}MHsyLC6Dg5=%JBUWkeE@Dmvw2|Gs)AES=a8O`K zL^Uy7XH?TNwg*+T>1p0R9=FWQN#k9P;|;ln8l!2_dnOp)@`>^Nqg;sgObb*S-}Lp} z`hPL^dS&k8VsF}~xbab~pg(L)#6D#OkV97OjcX8n-5RX&uR-m2AH$sS6Kw&KwQOex zVvGmuxdH5xCMIFL6B}^C6B1zx$JYny+z-H~ zUoN^fyF}6D^ckT3>Ne52T6DDgqTr(zbfAT(5P}{A+Di5MX2%w{Xu@iy>+>MX7m0~0 zUCp4}IO>~Sb2^y{<_l960Q4GXV}rxfCW;SZ^N%2S6v1Htbnh}-jd6R216QQ3-T|ta zPrbF#=@L;B5)UExF#?>X07{p$&cCKiixVHhJcC2@fO^K%DEjoWtVEx%xv|+*=X7}- zjm>Vc5)0`;Hx!j3zGYcGq3(7UARF(YZ@ee&hBndV3kKD0rAKsm8n%j0VlR#Wum>~M zPDGjJ3szRb12f>$EAL|4&PhtEU;oqjy8){Ov{Bbm?=w0(=%>r$48cISp0<`cs7Ci5 zHTQ@|0dH5z;~3E6y0KV=*axRedWCS^KBSF$KMgK8j1~%NmKxWyeRRLZOo5%uQmlH=#mKs2EA8MC`Gw9ICsD z<~qtJePp)MCB6EdF+SYOvrgCalm5bf7)}983#i&Ta(vDxpLvMY3OXc*^k=U}{&@n#BKRRe}`bpn4iqk5BH) z>E9(M7mvi1++^8o$0GZ74sg%U8(MsPvA1ZxTr__qX~8JJkd0AU1Z5o;j`EA>;L7jz z0LB?lmK`k{(2b-NkHj;qF|2ru%*S~7B$-b-me#Ku2p>tF3TpGDtPGjY7|vWY%HM(~ zfF!!num7jXd|IEQKfJ%P9~ALIrs=c81CNn^k~6D@Gv*H4 z=8eQu_p0v=fd=-6>;sJ(_3%eK`d0Tlo{t=gKOXNboGTa39kI?E<*UaF;pz;hVbXbP ziq~2tTdSZ1PEGtr2`POIc!(&Iv&y_#i{z|DuTLGeFB?u>K9W%5UwZnhAK6O%#~m+^ zQ+;e&f7C$I^S2IdIKIID3ZzYy{i_Pp$I`NZ zp8ZE9h2wBeM)_2K8dAQOXDi&uxB=!aa#Et7q|fqYw9*;Jts^vPbu1??r!||iX;Q+dQFy}uI7Ag^Yb=)iqUgWp`j@^61|a(>T)EWannuI?(kwcQD)aNDg5 zZS@Yf%V}$Co!H^s9?Zi19S-p}(ZysX9RDE+&-!jYwV{LY&-yo-NTJ9GpqrU^l-~?|7 z^%~;N7~&XFB*{XOSFp>1y+3VKD40YjDYB3KEy7~yvXFjO$Yg}`-&UMdFK|*# zsxxaBL5Ch7(q|SDO>c|k1rKT{>}gUn&Gk#cq}v8+zqqxV9#BV00}DtzeXBS|4~JVP z=j5JNQ)5XYJsO!P-JU}DAa`6hy}Zpr_q-L^God|t#%*o;g1S{dCGWwG&ZZ8x$H$`*;b?4i`-0lr z+&&X)$_*M%o#@)$;Q~{SQ^Z4t7?1TsSqDLFa=Yj406UUhtyp+8F8uNtl&(P426|vd zWY1rqPM7Tl>bMTcTSymp{{NC~-a`2PvVCRuls~!xnctf*T~3&O#xVVSY@#7Zjr(+H#L=AP(-pD^qT9pFM$f|8X2!Qf3SU7iL2&Rut1|ulR0~=(gM(Uu7SPQ6@B+e8jMC@1#M)!$y5_HR zd9G($U_O`F>S%86v}HEHAK>l!PFuS}glPqJTC_PDKpAtwy4dPnTOB(Z+eG_PyO!~= zFTBpt3g$B~&w1JumJ|;{U&R9m_9OT`0DBOuRSyzD;85Vx`Rln+u?*PTd;&;=aTf1N zxMBGwXZvvN8liT8r)5Ljpl~6Vj@Z4TGx#ip2!+6JzihJ!mQZ{o@CPO z_mk+)!y~CfYwgk4wOwhI3Slx?wiP5>2b{Kmq5~9YV^hzLhjz{vQL|iZ0}HdKQ>g~4 z;1g=*Ioz;|Rdt9vTy8Kes#+QWXGe=-_`di$TcQY`u)MRzGd-`yQw3)h@f#ITy7+g< z19LR%0^-6$-rNq+!OYKSL7#-$r>^a6clr2b9iFO=`o;!@vy4z^r3VgGs~pW8F2+j? z%e+>nlEPRFxy#3IhJLxXg19_}pJ6?9kN_t-IOzC|_!=0j=wWOK2_Iiqw` zC}SJ3pEZHAjp6><_5SL!Wc#d9q5OvGGv$n#qeAI;^-&>{DOrm4i`I27&ooU?mS@SC zvqt&y@e!aVc$QB@9gF;tNozG4ewa*t%P)Y{Ab^z=zeqOHPks`|iB=lEf2I_YN3Dp!OfdlRJM4|`Aci+mEb$msb@UzN-wNLG}70I@uv%)ksZGp6@vTf?9Pz2Kl zYgD?g^j@}Dx9q#+o+ZjMo)xk#1*wD>zl;I&_((5R5+gT<>8NUPk$<*85u$fzMM@)u z;MRl>rrAi&CB#gaS2lqqs8@m|md*EnSH1__zND)N_Rg+6(MzsHvKwq>_UrwA5>|%O>WYnJH(@9LXvhG0pOt z=F6t}udBTa*2oLij4W6;Vp=~etUo_qC+okjB%R74af8;?xrF?XE?HBceHJ<*zDVy} z(@pyBTbs$naf4ZF^Mb76J&^IWF2yxvO5xNZaR7^SE&&JE{gpTt+RiHWBW zoJQ~n0<>gJSfCWE6dKB4wv#X`6$n;LC-nY}Q?)2#yYlF_4KZ}TgQuTuG;&k9!GCOg zjH^h1Vtya#!+S&2}97ZZ$X9?k~ z!3Da`RZgCu54qC04sP(YD}}_~3mLUbL1mp(49m_W^q-rT;g-2<%N#YSwa3u&8D^(X z+g9HM<~*?!u#2Bz$pi#DX5}Pxuj`~8a(L`SfaS%<;mFl2P zL^JEk>>$0ye$`^oWPBbQs)cXzJFJ0rI)H_3zGd zXc;VbsLCs2QAi6v=U}I*5X?m|4?#5o9B#nc_g@shi!N*`CU*|r(e$l8_~JUK>}UgS zqTz=k2m3thIm7eVS}%g{4{CR6I4*}!W9J_%;vX=L5%Evqtt$xhUvX$q-&IILOwf_9 zNt*aFWL#7aF8EHNu7_#3j9?~=q-XiNwEOU&KJL3!4R59zQ7ZUA8g^BvHssVMJ4mWdjGux^7DkRfY zcYdGTNiW)x4$$@!f z51oJDT>};q*C6^%q}Sj|d_`~vU3##Gt0eTDgP$cb+Ja*c79Qu5fUr^yT7UY3(** zfM^pqnE%cyeQz6t=#1QN!FUw<(0frBDngq|OlVgz(1+hkrfVO_l0r5SfoL#{M#|p= zK_4ip;(6CZrUPmd@zMUI|4l)|+HQ^y_%#u%|s<3q)<1*O4oFk+8S zGq0uH(t+KVWTE=URO8zqN6HP1u8(8ID)f@jmmaYf zGvZ}b$SnMfco{9Ddqcb#Q8Ie;(wIjR$U!>e(S?;a5#>HTkg5`j!OZn`eiAJjx4Ili zG0HXzgh)?6nnS)zKYP@YjLY0*ncKFrwYkmVtm$xdxbgs1M|)jI<3mBPA;!@3!v$7H ze?pRP zM~;x}A3_&E)||lc-;Gq(i+~xSD)H@Y%=j+8d1&I%-2a4>HXW@a9}oWaXehUFFVyPN z*c!o$AYwB<;ugCg4Gm!v?R^h%U%*zdWgp+@UeM@tg2(AYNP~l55@umQgBxU;DiEM@ zfX+CUM!p%`cf^xe!P&=s0VldxPnYu z3^}+*vEehOWP^SfE8qbhkcZ+R+<4aaw{nnPezKM`s_Fdxdaj(%r~Bh);@IqhxBxr6 z5Wyk@GV&ZFQV^3%-Y`R5SoFb0e{>1sES1$edCyPNsO~r^hHH+7i-#4>yVT4>1 zbLuJ)a%z77pn>*4o5+6YcqS8J7Gy=(;UD82o7YFH@bEm6+$z#5*d^yu-FTpvqS^mmrZc3#xv z-7K)p?})4huWXx0e{=kv6kOK{vE^Rw*fJru@^E375xVoIDdaqT3G;^0KL(yL=68cbH2AfkFdnIg zQ>H1U!7Le5A;(mV#8mcz_r~In!E!S{-?d-S-f~vAuN>SLc-ZwjnEIyn2lb)nfnm4H zyuClEzb4Syq;Fa@xPZ$GlE?v_s zgqgPd&PZ=*(}zIh29Op$5bwh_WzJ)zrF~Y?5XA&gsnGTkyzElWgRo-cNn?UD% zV4-bag%Ufm6brAQniuWb6@c#IE}H*hr!Y>=Vhy1`doiB$4F2zn<)kN41Tg}(mvdl~Ql%61jAVw{*2!x_XpfGx(R6QVHpPa07_}3aX*E|2!RaA^+K)zV-8b zShs)td|?p8Fk?Uy-SBc2y}cxa-v9C@ri-mFHc&Ixl2!}|)OuneXkh~Nov_?@)kK3{wbEBk z{6q?A2I184>TX14pd}}xqysC6Sr_JH|7iZneRTG#7Knz!AZC!O8eO-psTkcf(i_3h z&dXmWu_oR+SrQ&TSqg&XA!0DBI+yL=0tM*7*+iO_jS4 zdf;_4?{0u-oMzaZ%&-5X7Rg842Tl3HL7DJC1WEHsmOsZK`h*>5ldRh6Xx$PpI);PW zMnO>5prre?KlJdiI2N&=o_l?P;0yK_t9OX>@}nm50&V*!mfn3lR@;kY_be5E^AFxJ z5HSF%Uh&}0-_{U}sI|m;BlG3Rd~al>99cOMSv4u-b;4R#E@y&(tDvvHF^_xm8ez3l zI?x0g-?=wufQT{BTi=S32I9aP4LcyUy=74%z?;@jp-tWl10fFGh6HLGRJH*2?VFJPcj%+L|Wa5$lglp>XSm!ThXQ#TIaH^X< zV4yqSYeV^=wDWg3*?snIgfzDW)(Veuzd2`s5Z{gGlIY161KoEnm)?F(PfwnUG(-dl zVz+^|i%~3}yuXv4|Er$veg7gc4{rOQfh6?8tD?a~qHAm&#!1KZoG{3c{4 zvx(WwH%8_|6}-l7#d}&x-Z1(KfKj}=N8-*Z1)kALxTox z&$yb|{v)2CAO8McG6XDN#8qeppZF+N!=(^<`r>KDI>S!Gz4Qm4BxsqO9HIlClxVkN zJ1^6}e3HBHrlfq6{xkWOZ2i>r=}hu6P-_mkZ}6>8+c@nDh~{}Z z=d(8QJpJis)1y$xGD{~j5ZTE^Jh^nG((uKx^hkB^V9H-sk>sxs+t&!b0pMqbJtB$+ z@momgnn`mmXV4#gW)e=oB#PilQ;CG57cLb={1a=Sh_KzJRJcepF6WQ|T6_7s ziKn7<}f+uhsN-Uq-R@*ZurOGj{4RLQj5eQOp8tTly(I|8)_p!l30F7YR9!L&*l<_!WSFNRe*a zPO|Bxhhk{hcS7j1-|nEpd&{Ng8i30a0D(~O zhZRm(%x-3HVDx6h!WR(PhX{U;fDztD_{L-I|HU^HON$hn7+d!@F);+Q(ZMg?g#hHE zN*uwrQ3Q-MaDP)Q70PuHaSRjsr7s9+)}DqgxSx^oJ)~~vR~&hlXi#r9(j#wVOKUWw zp8LBRLTuu28!@4`0$dkndZ|}G6sjdJ5VI2k!N(7wDt06_(l=WYX%rmQ*zEu~kfIi{ zyTphd56ckc$zG1L5y@g8_OxW^%EdoIui25Px+5I=f9c5mKkz$!;VcS4vXaw4s?BH# zwac3>^$3w-Xy%FxDdp`VqLiqica+50r`jl=34;HW$iC8zM+L zw^TcHR|J88m}A&|X4R;IYHs)nIK84dsU?=ghhNw8M(t3fnLJJkn8{G@<3)%b5h0w( zrx8(^QD_6LEIutB=qo+q4%UYB$4HVA^ACJK2;ka1&%{a1Y0$4Wt+dzzrpE4f_1xcs zq(50mPU1xPI=Ii(0e!%&+k)Cc;BtwLe)(9WbVn4i#CHY5DFqHcP7PjDIY7iRxc;E+ z4vJOnR@1z`EF>3=dai2Y0qJ`g#6`69($kT2&s!GS`b8qDsi0gia)ir!43|y{<4M$g zSIyG0OcKJ~r>41m*>qX4Ah~Y^)wy1qzzQg)5sVF^pFU{DWH2S{B2odk>{C2;m|6C8 zs{?ym7cgf7SBHFxkF*zVA+m^*;B6@3QosJsbldS?%Fio2%2O^RKbbTn8)K9-wImB!bXhH{-6Q)l8xO0?~ZUL9f^`o9v(Vna8d)W9h^b?-C>ZnTp9Zou&gzZLmt*q>QoU0a$nan60*KBv#2%^{F(i&9 z+K01d_E!uimi8`#!Vu-j;_tFuUa z>{wWwH!NQc%lC%Oki%xYr1loyDi_~68n#MuXOV&)BUZW;aXn765<3uRXPPWb8x>}Z z#{+qVTv^B+74j#g_{%UCdGZ+{`B+1L{z&>vS(tfNNQPcR$f+SoHY8!pQHh*da>h_H zCO9EDHBA=M`hy2b2Xcoi9K-dSz4g1~`d!10&f)Z~OCS+QH3tzv#`zpWePijJa{bO> z$1W(^Np9ibPsU<|OTmqa_119GJyS^>d6-^U8;?8It#sR)hLA}CJ)(`p83u{wZfY|| z<&QHq$f?`Zt%h#0i?}(WVsb0M^ePYT(D*3qkJq~l;97C2S^Qd+bTENfz{116OyIQ# zt4svDq6}n*4^edjeoW?^I#${Q!h$~xC;Wh@gd);`1QHe8%`w3;y_;hN73l_cU#!hL zAscEMSXMl|3Ee>ejfZuUF|}Hh;vvouRy$90Vp@-~d zuZW6KE#qKGu##oCiEXr!8S|0*#cUW*M;wf3TwYu+C_J{V2@!!@%;dz^S_r%ZQPGhm zIjkgpO+d6j9G>lKK7b4y6W8t8xhP1OH0j6Y880(wf(-2oM{6}RRfCO+9;qRT*p~vU3Z?@^!!h}zhJt}w@3f_7rY#*yPWL94%E_h3 zxEM1hhuP03+aJsAU%^s7iXOlDIuc#bYdmj=K2&+ekT514NP-K2TIqNa++7_f`z2=# zg=5hy3LTH?a&&p`LdHIRik>x)KU}$Cc%#F+(JgOu4{z`Q5qFUJNXrYj8vHY#TY-O8 zaBJ{S##)5yAs`mX+%>@-!LWd?(^7Fm~8Tn3Bmz zl4;&&ah`N7in&wS?ii`ggh?C^;00q+4;&fKVcBrMaR@^38qlo2#pPBbSHYv4ki1Z; z%NjT|D>nBVOi%A1S!0NbKZwQ(cW^LnZ04DanHU`!8FQU6slGQDiNoSGB*=z@5h$H> z#*i{*1DUK?E=Dr(_Vloja6Z)5S9K;dqkpb9XO5gR=e3ouo4pG+$O|`&%-eV-XCn(M zO_#&c&xU0JG39O|!=V|MbSi=VDLbA^#v?;4H?f{tbX?IKqcwCl@u0<+JrHY&LEl=8 zO!gSYmrZ&<9n`bJz6`XYC*XmAJy3c)_7oJuBe?iTFTveGF;HN)+GC>c)J4%18Z-N) zh4G+D19+&9z8%RD>02k0=sTcFe*3u@iHLWqnfj=uZ$E7TAC5V^8*au<2<3Z^UaLq> z(jR58p5eV@cGCbNGto}U9B=3$55=FufYnzX|KM=nAUb+Mun65cMze{2z0j1Q>DGZD z*Mg+hO|;JZeE}q}<$)0gDhIP3^MR`Ha~Y609ct?tj^J({-VLxN{2NSmp(|}MNKffd zCQ0UN6~(Yax@;wIHLL`AALV@qdKXbs49M#)wXJQltt&Dhv9D1E#bP9P%OvJNQSA$Y zATmV{WIVynrg_)`lQ`F^e_}qTD)5H_6=0bOfi$k(iG`TK_>QS{da2Y-5_%NbQ-pcb z5KKoJzsRMJpMKy9!&8VeQ~`^yR}V){hDsE(NP>X}#*ReEcPy)L%df z(mlf+w-0-IU^kl0RkN*$?OF?AySfTn(@O3az^|+@jK6+nJldM5q)Us5MJvE>8i1UK`2QYIQ?4lhR|5bA@YV!ZKxE6c-*!#c zQonQ`kLk~KdpZNxJn;Nr+qu;xy5M#XTnA`tb+M=gh>675|B29Pho{Hpuz4C=T-CKA z{vgg$2oPYV1tnY^QpO_&{C7FGwG!Zj}R zCY8xaWy1+&%-;%kv6OplGftU@`Bb>v2I0*@;#l^~-nn@5Cp(}06;q|3mY{%xJzBc0 zkUTb>DTf{Ck^b=Ty?zr7oEPB&taGMKvAwFV*WtjNQ9K3gC-}=EU5QL9@!Cu@v9d;xJG^-o~V(%{E&Zc0MOvE+aNBSvO>wMZV9KE=CM! zG(|=z#;W5p`IHhmBjUm_)c$#~DX5l9V-+NmTcwk@+2mugMEYblNuD}hTf~?YaRb!# zGn9DC&GFPC8KiNWbkgQZ@QvIzLjj8%}yxm!y6G0ZND267{M1H z2K)^IHfIov+zjoC>!i$OV3oaJH}v!}5=7=s;Ql8TWd`*~d~3qejM~t6su(3P_$Lt5 z%u2Rkq3zN)%Sop8Imqlvm2QiG{h+)Crf#QhXju)pL#uln5g0jXY&A)%W+u)stXGZA zgyUNTf+w)lj}frt!jy8T=vAw%ZBq{p)#@wmIQfd|DT$O3ylp$b2+IaqZ**LiS0= zx0BCn*i@jMU5P_xllNalfU;?14q!HoYzCQ++L@4ZVI!zmHk3s-#jk zX(KO6PrJ$OTrEG8?Ezmv7)2UloO(aJH9+XjyXK*7J4mH^4thx0y#f~as%&J)`U+{W zSZ(cQv8@HR*q3o4$A2cI&{pY?=*pV%#`5#k1-E z35sjG&O8H3n2iB{xy&Ex3cnU2ecDap)Q>@R{D}!pyjQaACgA5ATC$rwNP6x>yvK1| zFCcgW!4QI%5nM!Iz)50WFH{D^Xapt%Q3%kkBjU+eMAcnn2L#kAL<<5qI;h0u2x<|a z*cVZ7EAHFpNCLce;5Rb9xDUaD2+-RJUG3>1F+F{V96=P!m>bug1!bBcC^IKZlXYbc z$DTbF{6D_`CbwW`D^!*?X$Eb`@oK?s>Sg4>IPxCnnWL%epQ8kuR8fkj^rGu YzN*5%SF6>_O~ia){ue6z%bW-Q4=F%Kng9R* delta 14717 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 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")