This commit is contained in:
Broque Thomas 2025-07-27 16:26:44 -07:00
parent 054665e494
commit b5ed2b0e5f
6 changed files with 18429 additions and 1 deletions

View file

@ -542,7 +542,7 @@ class SoulseekClient:
return None
async def search(self, query: str, timeout: int = 35, progress_callback=None) -> tuple[List[TrackResult], List[AlbumResult]]:
async def search(self, query: str, timeout: int = 60, progress_callback=None) -> tuple[List[TrackResult], List[AlbumResult]]:
if not self.base_url:
logger.error("Soulseek client not configured")
return [], []
@ -937,6 +937,207 @@ class SoulseekClient:
logger.error(f"Error clearing completed downloads: {e}")
return False
async def get_all_searches(self) -> List[dict]:
"""Get all search history from slskd
Returns:
List[dict]: List of search objects from slskd API, empty list if error
"""
if not self.base_url:
logger.error("Soulseek client not configured")
return []
try:
endpoint = 'searches'
logger.debug(f"Getting all searches with endpoint: {endpoint}")
response = await self._make_request('GET', endpoint)
if response is not None:
searches = response if isinstance(response, list) else []
logger.info(f"Retrieved {len(searches)} searches from slskd")
return searches
else:
logger.error("Failed to retrieve searches from slskd")
return []
except Exception as e:
logger.error(f"Error retrieving searches: {e}")
return []
async def delete_search(self, search_id: str) -> bool:
"""Delete a specific search from slskd history
Args:
search_id: The ID of the search to delete
Returns:
bool: True if deletion was successful, False otherwise
"""
if not self.base_url:
logger.error("Soulseek client not configured")
return False
try:
endpoint = f'searches/{search_id}'
logger.debug(f"Deleting search {search_id} with endpoint: {endpoint}")
response = await self._make_request('DELETE', endpoint)
success = response is not None
if success:
logger.debug(f"Successfully deleted search {search_id}")
else:
logger.warning(f"Failed to delete search {search_id}")
return success
except Exception as e:
logger.error(f"Error deleting search {search_id}: {e}")
return False
async def clear_all_searches(self) -> bool:
"""Clear all search history from slskd
Returns:
bool: True if all searches were cleared successfully, False otherwise
"""
if not self.base_url:
logger.error("Soulseek client not configured")
return False
try:
# Get all searches first
searches = await self.get_all_searches()
if not searches:
logger.info("No searches found to clear")
return True
logger.info(f"Clearing {len(searches)} searches from slskd...")
# Delete each search individually
deleted_count = 0
failed_count = 0
for search in searches:
search_id = search.get('id')
if search_id:
success = await self.delete_search(search_id)
if success:
deleted_count += 1
else:
failed_count += 1
else:
logger.warning("Search found without ID, skipping")
failed_count += 1
logger.info(f"Search cleanup complete: {deleted_count} deleted, {failed_count} failed")
return failed_count == 0
except Exception as e:
logger.error(f"Error clearing all searches: {e}")
return False
async def maintain_search_history(self, max_searches: int = 50) -> bool:
"""Maintain a rolling window of recent searches by deleting oldest when over limit
Args:
max_searches: Maximum number of searches to keep (default: 50)
Returns:
bool: True if maintenance was successful, False otherwise
"""
if not self.base_url:
logger.debug("Soulseek client not configured, skipping search maintenance")
return False
try:
# Get all searches (should be ordered by creation time, oldest first)
searches = await self.get_all_searches()
if len(searches) <= max_searches:
logger.debug(f"Search count ({len(searches)}) within limit ({max_searches}), no maintenance needed")
return True
# Calculate how many to delete
excess_count = len(searches) - max_searches
oldest_searches = searches[:excess_count] # Get the oldest ones
logger.info(f"Maintaining search history: deleting {excess_count} oldest searches (keeping {max_searches})")
# Delete the oldest searches
deleted_count = 0
failed_count = 0
for search in oldest_searches:
search_id = search.get('id')
if search_id:
success = await self.delete_search(search_id)
if success:
deleted_count += 1
else:
failed_count += 1
else:
logger.warning("Search found without ID during maintenance, skipping")
failed_count += 1
logger.info(f"Search maintenance complete: {deleted_count} deleted, {failed_count} failed")
return failed_count == 0
except Exception as e:
logger.error(f"Error during search history maintenance: {e}")
return False
async def maintain_search_history_with_buffer(self, keep_searches: int = 50, trigger_threshold: int = 200) -> bool:
"""Maintain search history with a buffer - only clean when searches exceed threshold
Args:
keep_searches: Number of searches to keep after cleanup (default: 50)
trigger_threshold: Only trigger cleanup when search count exceeds this (default: 200)
Returns:
bool: True if maintenance was successful or not needed, False otherwise
"""
if not self.base_url:
logger.debug("Soulseek client not configured, skipping search maintenance")
return False
try:
# Get all searches
searches = await self.get_all_searches()
if len(searches) <= trigger_threshold:
logger.debug(f"Search count ({len(searches)}) below trigger threshold ({trigger_threshold}), no maintenance needed")
return True
# Calculate how many to delete (keep only the most recent ones)
excess_count = len(searches) - keep_searches
oldest_searches = searches[:excess_count] # Get the oldest ones to delete
logger.info(f"Search buffer exceeded: {len(searches)} searches > {trigger_threshold} threshold. Deleting {excess_count} oldest searches (keeping {keep_searches})")
# Delete the oldest searches
deleted_count = 0
failed_count = 0
for search in oldest_searches:
search_id = search.get('id')
if search_id:
success = await self.delete_search(search_id)
if success:
deleted_count += 1
else:
failed_count += 1
else:
logger.warning("Search found without ID during maintenance, skipping")
failed_count += 1
logger.info(f"Search buffer maintenance complete: {deleted_count} deleted, {failed_count} failed, {keep_searches} searches remaining")
return failed_count == 0
except Exception as e:
logger.error(f"Error during search history buffer maintenance: {e}")
return False
async def search_and_download_best(self, query: str, preferred_quality: str = 'flac') -> Optional[str]:
results = await self.search(query)

18170
logs/app.log

File diff suppressed because it is too large Load diff

57
main.py
View file

@ -68,6 +68,58 @@ class MainWindow(QMainWindow):
self.status_thread = None
self.init_ui()
self.setup_status_monitoring()
# Setup periodic search maintenance (rolling 50-search window)
self.setup_search_maintenance()
def setup_search_maintenance(self):
"""Setup periodic search history maintenance to keep only the 50 most recent searches"""
try:
# Create timer for periodic search maintenance
self.search_maintenance_timer = QTimer()
self.search_maintenance_timer.timeout.connect(self._run_search_maintenance)
# Run maintenance every 2 minutes (120 seconds)
# This keeps search history clean without being too frequent
self.search_maintenance_timer.start(120000)
logger.info("Search maintenance timer started (every 2 minutes, keeps 200 most recent searches)")
except Exception as e:
logger.error(f"Error setting up search maintenance: {e}")
def _run_search_maintenance(self):
"""Run search maintenance in background thread to avoid blocking UI"""
try:
# Only run if Soulseek client seems to be available
if hasattr(self.soulseek_client, 'base_url') and self.soulseek_client.base_url:
# Run maintenance in background thread
import threading
def maintenance_thread():
try:
import asyncio
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# Run the maintenance (keep 200 most recent searches)
success = loop.run_until_complete(self.soulseek_client.maintain_search_history(200))
if not success:
logger.warning("Search maintenance completed with some failures")
except Exception as e:
logger.error(f"Error in search maintenance thread: {e}")
finally:
loop.close()
thread = threading.Thread(target=maintenance_thread, daemon=True)
thread.start()
else:
logger.debug("Soulseek client not configured, skipping search maintenance")
except Exception as e:
logger.error(f"Error running search maintenance: {e}")
def init_ui(self):
self.setWindowTitle("NewMusic - Music Sync & Manager")
@ -204,6 +256,11 @@ class MainWindow(QMainWindow):
logger.info("Stopping status monitoring thread...")
self.status_thread.stop()
# Stop search maintenance timer
if hasattr(self, 'search_maintenance_timer') and self.search_maintenance_timer:
logger.info("Stopping search maintenance timer...")
self.search_maintenance_timer.stop()
# Close Soulseek client
try:
logger.info("Closing Soulseek client...")