def create_default_config(): return { 'input': '/volume1/downloads/music', 'output': '/volume1/docker/navidrome/music', 'trash': '/volume1/docker/navidrome/trash', 'schedule': { 'enabled': True, 'type': 'daily', 'dayOfWeek': '1', 'time': '02:00', 'cron': '0 2 * * *' }, 'advancedStrategy': { 'metadataFallback': True, 'downloadAssets': True, 'replaceLowQualityDuplicates': False }, 'notifications': { 'dingtalkWebhook': '', 'dingtalkSecret': '', 'telegramBotToken': '', 'telegramChatId': '', 'emailSmtp': '', 'emailUser': '', 'emailPass': '', 'emailTo': '' }, 'metadata': { 'acoustidUrl': 'https://api.acoustid.org/v2', 'acoustidClientKey': '', 'musicbrainz': 'https://musicbrainz.org/ws/2/', 'netease': 'http://localhost:3000', 'qq': 'http://localhost:3300', 'spotifyUrl': 'https://api.spotify.com/v1', 'spotifyClientId': '', 'spotifySecret': '', 'discogsUrl': 'https://api.discogs.com', 'discogsToken': '', 'lastfmUrl': 'https://ws.audioscrobbler.com/2.0/', 'lastfmKey': '', 'geniusUrl': 'https://api.genius.com', 'geniusToken': '' } } def merge_config(partial_config): defaults = create_default_config() merged = defaults | { key: value for key, value in partial_config.items() if key in defaults } for nested_key in ('schedule', 'advancedStrategy', 'notifications', 'metadata'): nested_value = partial_config.get(nested_key, {}) if isinstance(nested_value, dict): merged[nested_key] = defaults[nested_key] | nested_value return merged def derive_task_state(config): return ( 'ready' if config['input'].strip() and config['output'].strip() and config['trash'].strip() else 'unconfigured' )