diff --git a/mopidy/__main__.py b/mopidy/__main__.py index 1bfcfbcf..30fcd7f9 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -1,12 +1,9 @@ from __future__ import unicode_literals -import codecs -import ConfigParser as configparser import logging import optparse import os import signal -import StringIO import sys import gobject @@ -29,12 +26,10 @@ sys.path.insert( from mopidy import exceptions, ext from mopidy.audio import Audio -from mopidy.config import default_config, config_schemas -from mopidy import config as config_utils # TODO: cleanup +from mopidy import config as config_lib from mopidy.core import Core from mopidy.utils import deps, log, path, process, versioning - logger = logging.getLogger('mopidy.main') @@ -51,16 +46,19 @@ def main(): try: create_file_structures() - logging_config = load_config(config_files, config_overrides) + logging_config = config_lib.load(config_files, config_overrides) log.setup_logging( logging_config, options.verbosity_level, options.save_debug_log) extensions = ext.load_extensions() - raw_config = load_config(config_files, config_overrides, extensions) + raw_config = config_lib.load(config_files, config_overrides, extensions) extensions = ext.filter_enabled_extensions(raw_config, extensions) - config = validate_config(raw_config, config_schemas, extensions) + config = config_lib.validate( + raw_config, config_lib.config_schemas, extensions) log.setup_log_levels(config) check_old_locations() + # TODO: wrap config in RO proxy. + # Anything that wants to exit after this point must use # mopidy.utils.process.exit_process as actors have been started. audio = setup_audio(config) @@ -83,9 +81,7 @@ def main(): def check_config_override(option, opt, override): try: - section, remainder = override.split('/', 1) - key, value = remainder.split('=', 1) - return (section, key, value) + return config_lib.parse_override(override) except ValueError: raise optparse.OptionValueError( 'option %s: must have the format section/key=value' % opt) @@ -181,73 +177,6 @@ def check_old_locations(): 'for further instructions.', old_settings_file) -def load_config(files, overrides, extensions=None): - parser = configparser.RawConfigParser() - - files = [path.expand_path(f) for f in files] - sources = ['builtin-defaults'] + files + ['command-line'] - logger.info('Loading config from: %s', ', '.join(sources)) - - # Read default core config - parser.readfp(StringIO.StringIO(default_config)) - - # Read default extension config - for extension in extensions or []: - parser.readfp(StringIO.StringIO(extension.get_default_config())) - - # Load config from a series of config files - for filename in files: - # TODO: if this is the initial load of logging config we might not have - # a logger at this point, we might want to handle this better. - try: - filehandle = codecs.open(filename, encoding='utf-8') - parser.readfp(filehandle) - except IOError: - logger.debug('Config file %s not found; skipping', filename) - continue - except UnicodeDecodeError: - logger.error('Config file %s is not UTF-8 encoded', filename) - sys.exit(1) - - raw_config = {} - for section in parser.sections(): - raw_config[section] = dict(parser.items(section)) - - for section, key, value in overrides or []: - raw_config.setdefault(section, {})[key] = value - - return raw_config - - -def validate_config(raw_config, schemas, extensions=None): - # Collect config schemas to validate against - sections_and_schemas = schemas.items() - for extension in extensions or []: - sections_and_schemas.append( - (extension.ext_name, extension.get_config_schema())) - - # Get validated config - config = {} - errors = {} - for section_name, schema in sections_and_schemas: - if section_name not in raw_config: - errors[section_name] = {section_name: 'section not found'} - try: - items = raw_config[section_name].items() - config[section_name] = schema.convert(items) - except exceptions.ConfigError as error: - errors[section_name] = error - - if errors: - for section_name, error in errors.items(): - logger.error('[%s] config errors:', section_name) - for key in error: - logger.error('%s %s', key, error[key]) - sys.exit(1) - - return config - - def create_file_structures(): path.get_or_create_dir('$XDG_DATA_DIR/mopidy') path.get_or_create_file('$XDG_CONFIG_DIR/mopidy/mopidy.conf') diff --git a/mopidy/config/__init__.py b/mopidy/config/__init__.py index 82fc839e..5ac492d4 100644 --- a/mopidy/config/__init__.py +++ b/mopidy/config/__init__.py @@ -1,7 +1,16 @@ from __future__ import unicode_literals +import codecs +import ConfigParser as configparser +import io +import logging +import sys + from mopidy.config.schemas import * from mopidy.config.values import * +from mopidy.utils import path + +logger = logging.getLogger('mopdiy.config') default_config = """ @@ -44,3 +53,84 @@ config_schemas['proxy']['password'] = String(optional=True, secret=True) # NOTE: if multiple outputs ever comes something like LogLevelConfigSchema #config_schemas['audio.outputs'] = config.AudioOutputConfigSchema() + + +# TODO: update API to load(files, defaults, overrides) this should not need to +# know about extensions +def load(files, overrides, extensions=None): + parser = configparser.RawConfigParser() + + files = [path.expand_path(f) for f in files] + sources = ['builtin-defaults'] + files + ['command-line'] + logger.info('Loading config from: %s', ', '.join(sources)) + + # Read default core config + parser.readfp(io.StringIO(default_config)) + + # Read default extension config + for extension in extensions or []: + parser.readfp(io.StringIO(extension.get_default_config())) + + # Load config from a series of config files + for filename in files: + # TODO: if this is the initial load of logging config we might not have + # a logger at this point, we might want to handle this better. + try: + filehandle = codecs.open(filename, encoding='utf-8') + parser.readfp(filehandle) + except IOError: + logger.debug('Config file %s not found; skipping', filename) + continue + except UnicodeDecodeError: + logger.error('Config file %s is not UTF-8 encoded', filename) + sys.exit(1) + + raw_config = {} + for section in parser.sections(): + raw_config[section] = dict(parser.items(section)) + + # TODO: move out of file loading code? + for section, key, value in overrides or []: + raw_config.setdefault(section, {})[key] = value + + return raw_config + + +# TODO: switch API to validate(raw_config, schemas) this should not need to +# know about extensions +def validate(raw_config, schemas, extensions=None): + # Collect config schemas to validate against + sections_and_schemas = schemas.items() + for extension in extensions or []: + sections_and_schemas.append( + (extension.ext_name, extension.get_config_schema())) + + # Get validated config + config = {} + errors = {} + for section_name, schema in sections_and_schemas: + if section_name not in raw_config: + errors[section_name] = {section_name: 'section not found'} + try: + items = raw_config[section_name].items() + config[section_name] = schema.convert(items) + except exceptions.ConfigError as error: + errors[section_name] = error + + if errors: + # TODO: raise error instead. + #raise exceptions.ConfigError(errors) + for section_name, error in errors.items(): + logger.error('[%s] config errors:', section_name) + for key in error: + logger.error('%s %s', key, error[key]) + sys.exit(1) + + return config + + +def parse_override(override): + """Parse section/key=value override.""" + section, remainder = override.split('/', 1) + key, value = remainder.split('=', 1) + return (section, key, value)