""" Spotify Downloader Settings API endpoints """ from flask import Blueprint, request, jsonify from flask_openapi3 import APIBlueprint, Tag from pydantic import BaseModel, Field from typing import Optional, Dict, Any from swingmusic import logger from swingmusic.config import UserConfig spotify_settings_bp = APIBlueprint( 'spotify_settings', import_name='spotify_settings', url_prefix='/api/settings/spotify' ) class SpotifySettingsRequest(BaseModel): defaultQuality: str = Field('flac', description='Default download quality') downloadFolder: Optional[str] = Field(None, description='Download folder path') autoAddToLibrary: bool = Field(True, description='Auto-add downloads to library') maxConcurrentDownloads: int = Field(3, description='Max concurrent downloads') sources: Optional[list] = Field(None, description='Download sources configuration') maxRetryAttempts: int = Field(3, description='Max retry attempts') cleanupHistoryDays: int = Field(30, description='Auto-cleanup history days') showExplicitWarning: bool = Field(True, description='Show explicit content warning') class SpotifySettingsResponse(BaseModel): success: bool settings: Optional[Dict[str, Any]] = None message: Optional[str] = None # Default settings DEFAULT_SETTINGS = { 'defaultQuality': 'flac', 'downloadFolder': '', 'autoAddToLibrary': True, 'maxConcurrentDownloads': 3, 'sources': [ { 'name': 'tidal', 'display_name': 'Tidal', 'enabled': True, 'priority': 1, 'config': { 'quality_preference': ['lossless', 'high', 'normal'], 'formats': ['flac', 'mp3'] } }, { 'name': 'qobuz', 'display_name': 'Qobuz', 'enabled': True, 'priority': 2, 'config': { 'quality_preference': ['lossless', 'high', 'normal'], 'formats': ['flac', 'mp3'] } }, { 'name': 'amazon', 'display_name': 'Amazon Music', 'enabled': False, 'priority': 3, 'config': { 'quality_preference': ['high', 'normal'], 'formats': ['mp3', 'aac'] } } ], 'maxRetryAttempts': 3, 'cleanupHistoryDays': 30, 'showExplicitWarning': True } def get_spotify_settings(): """Get Spotify downloader settings from config""" try: config = UserConfig() spotify_settings = config.spotify_downloads if hasattr(config, 'spotify_downloads') else {} # Merge with defaults settings = {**DEFAULT_SETTINGS} settings.update(spotify_settings) return settings except Exception as e: logger.error(f"Error loading Spotify settings: {e}") return DEFAULT_SETTINGS def save_spotify_settings(settings_data: dict): """Save Spotify downloader settings to config""" try: config = UserConfig() # Update only provided settings current_settings = get_spotify_settings() current_settings.update(settings_data) # Save to config config.spotify_downloads = current_settings config.save() logger.info("Spotify settings saved successfully") return True except Exception as e: logger.error(f"Error saving Spotify settings: {e}") return False @spotify_settings_bp.get('/', summary='Get Spotify downloader settings') def get_settings(): """ Get current Spotify downloader settings Returns all Spotify downloader configuration including: - Default quality settings - Download folder configuration - Source priorities and enablement - Advanced options """ try: settings = get_spotify_settings() return jsonify({ 'success': True, 'settings': settings }) except Exception as e: logger.error(f"Error getting Spotify settings: {e}") return jsonify({ 'success': False, 'message': str(e) }), 500 @spotify_settings_bp.post('/', summary='Update Spotify downloader settings') def update_settings(body: SpotifySettingsRequest): """ Update Spotify downloader settings - **defaultQuality**: Default download quality (flac, mp3_320, mp3_128) - **downloadFolder**: Custom download folder path - **autoAddToLibrary**: Whether to auto-add downloads to library - **maxConcurrentDownloads**: Maximum concurrent downloads (1-10) - **sources**: Download sources configuration - **maxRetryAttempts**: Maximum retry attempts for failed downloads - **cleanupHistoryDays**: Days to keep download history (0 = disabled) - **showExplicitWarning**: Show warning for explicit content Updates the Spotify downloader configuration and saves to user settings. """ try: # Validate inputs if body.defaultQuality not in ['flac', 'mp3_320', 'mp3_128']: return jsonify({ 'success': False, 'message': 'Invalid quality setting' }), 400 if not 1 <= body.maxConcurrentDownloads <= 10: return jsonify({ 'success': False, 'message': 'Max concurrent downloads must be between 1 and 10' }), 400 if not 0 <= body.maxRetryAttempts <= 10: return jsonify({ 'success': False, 'message': 'Max retry attempts must be between 0 and 10' }), 400 if not 0 <= body.cleanupHistoryDays <= 365: return jsonify({ 'success': False, 'message': 'Cleanup days must be between 0 and 365' }), 400 # Prepare settings data settings_data = { 'defaultQuality': body.defaultQuality, 'downloadFolder': body.downloadFolder, 'autoAddToLibrary': body.autoAddToLibrary, 'maxConcurrentDownloads': body.maxConcurrentDownloads, 'sources': body.sources, 'maxRetryAttempts': body.maxRetryAttempts, 'cleanupHistoryDays': body.cleanupHistoryDays, 'showExplicitWarning': body.showExplicitWarning } # Remove None values settings_data = {k: v for k, v in settings_data.items() if v is not None} # Save settings if save_spotify_settings(settings_data): return jsonify({ 'success': True, 'message': 'Settings saved successfully' }) else: return jsonify({ 'success': False, 'message': 'Failed to save settings' }), 500 except Exception as e: logger.error(f"Error updating Spotify settings: {e}") return jsonify({ 'success': False, 'message': str(e) }), 500 @spotify_settings_bp.post('/reset', summary='Reset Spotify settings to defaults') def reset_settings(): """ Reset all Spotify downloader settings to default values Resets all Spotify downloader configuration to factory defaults. """ try: if save_spotify_settings(DEFAULT_SETTINGS): return jsonify({ 'success': True, 'message': 'Settings reset to defaults', 'settings': DEFAULT_SETTINGS }) else: return jsonify({ 'success': False, 'message': 'Failed to reset settings' }), 500 except Exception as e: logger.error(f"Error resetting Spotify settings: {e}") return jsonify({ 'success': False, 'message': str(e) }), 500 @spotify_settings_bp.delete('/queue', summary='Clear download queue') def clear_queue(): """ Clear the entire download queue Removes all pending and active downloads from the queue. """ try: from swingmusic.services.spotify_downloader import spotify_downloader # Clear queue spotify_downloader.download_queue.clear() return jsonify({ 'success': True, 'message': 'Download queue cleared' }) except Exception as e: logger.error(f"Error clearing download queue: {e}") return jsonify({ 'success': False, 'message': str(e) }), 500 @spotify_settings_bp.delete('/history', summary='Clear download history') def clear_history(): """ Clear the download history Removes all completed and failed downloads from history. """ try: from swingmusic.services.spotify_downloader import spotify_downloader # Clear history spotify_downloader.download_history.clear() return jsonify({ 'success': True, 'message': 'Download history cleared' }) except Exception as e: logger.error(f"Error clearing download history: {e}") return jsonify({ 'success': False, 'message': str(e) }), 500 @spotify_settings_bp.get('/sources', summary='Get available download sources') def get_available_sources(): """ Get list of available download sources Returns information about supported download sources and their capabilities. """ try: sources = [ { 'name': 'tidal', 'display_name': 'Tidal', 'description': 'High-quality FLAC downloads from Tidal', 'quality_options': ['lossless', 'high', 'normal'], 'formats': ['flac', 'mp3'], 'available': True, 'requires_auth': False, 'max_quality': 'lossless' }, { 'name': 'qobuz', 'display_name': 'Qobuz', 'description': 'Alternative high-quality source with extensive catalog', 'quality_options': ['lossless', 'high', 'normal'], 'formats': ['flac', 'mp3'], 'available': True, 'requires_auth': True, 'max_quality': 'lossless' }, { 'name': 'amazon', 'display_name': 'Amazon Music', 'description': 'Fallback source with wide availability', 'quality_options': ['high', 'normal'], 'formats': ['mp3', 'aac'], 'available': False, # Disabled by default 'requires_auth': True, 'max_quality': 'high' } ] return jsonify({ 'success': True, 'sources': sources }) except Exception as e: logger.error(f"Error getting available sources: {e}") return jsonify({ 'success': False, 'message': str(e) }), 500 # Error handlers @spotify_settings_bp.errorhandler(400) def bad_request(error): return jsonify({ 'error': 'Bad request', 'message': str(error), 'success': False }), 400 @spotify_settings_bp.errorhandler(500) def internal_error(error): return jsonify({ 'error': 'Internal server error', 'message': str(error), 'success': False }), 500