X-Git-Url: https://git.kaliko.me/?a=blobdiff_plain;f=sima%2Futils%2Fconfig.py;h=fd5cab6c9056d0129a71fea98a328c9f822fe258;hb=b374f36f34ce368fbfc2b8b56b784aa45b27ec7e;hp=46bddf67954ff39223f89b15dd0d87bf1b409c40;hpb=cd78ebfec21926f231f7b5f1430d31baee4b60c4;p=mpd-sima.git diff --git a/sima/utils/config.py b/sima/utils/config.py index 46bddf6..fd5cab6 100644 --- a/sima/utils/config.py +++ b/sima/utils/config.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- - -# Copyright (c) 2009, 2010, 2011, 2013 Jack Kaliko +# Copyright (c) 2009-2015, 2019-2021 kaliko +# Copyright (c) 2019 sacha # # This file is part of sima # @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with sima. If not, see . # -# +# pylint: disable=bad-continuation """ Deal with configuration and data files. @@ -26,60 +26,92 @@ Parse configuration file and set defaults for missing options. # IMPORTS import configparser +import logging import sys from configparser import Error -from os import (makedirs, environ, stat, chmod) -from os.path import (join, isdir, isfile) +from os import (access, makedirs, getenv, stat, chmod, W_OK) +from os.path import (join, isdir, isfile, dirname, exists) from stat import (S_IMODE, ST_MODE, S_IRWXO, S_IRWXG) from . import utils # DEFAULTS -DIRNAME = 'sima' -CONF_FILE = 'sima.cfg' +DIRNAME = 'mpd_sima' +CONF_FILE = 'mpd_sima.cfg' DEFAULT_CONF = { 'MPD': { 'host': "localhost", #'password': "", - 'port': "6600", + 'port': 6600, }, 'sima': { - 'internal': "Crop, History, MpdOptions, Lastfm, RandomFallBack", + 'internal': "Crop, Lastfm, Random", 'contrib': "", - 'user_db': "false", - 'history_duration': "8", - 'queue_length': "1", + 'user_db': False, + 'history_duration': 8, + 'queue_length': 2, + 'var_dir': 'empty', + 'musicbrainzid': True, + 'repeat_disable_queue': True, + 'single_disable_queue': True, + 'mopidy_compat': False, }, - 'daemon':{ - 'daemon': "false", + 'daemon': { + 'daemon': False, 'pidfile': "", }, 'log': { 'verbosity': "info", 'logfile': "", }, - 'echonest': { + 'crop': { + 'consume': 10, + 'priority': 0, }, 'lastfm': { - 'dynamic': "10", - 'similarity': "15", - 'queue_mode': "track", #TODO control values - 'single_album': "false", - 'track_to_add': "1", - 'album_to_add': "1", - 'depth': "1", + 'queue_mode': "track", # TODO control values + 'max_art': 10, + 'single_album': False, + 'track_to_add': 1, + 'album_to_add': 1, + 'shuffle_album': False, + 'track_to_add_from_album': 0, # <=0 means keep all + 'depth': 1, + 'cache': True, + 'priority': 100, + }, + 'random': { + 'flavour': "sensible", # in pure, sensible + 'track_to_add': 1, + 'priority': 50, + }, + 'tags': { + 'comment': "", + 'date': "", + 'genre': "", + 'label': "", + 'originaldate': "", + 'filter': "", + 'queue_mode': "track", + 'single_album': False, + 'track_to_add': 1, + 'album_to_add': 1, + 'priority': 80, + }, + 'genre': { + 'queue_mode': "track", + 'single_album': False, + 'track_to_add': 1, + 'album_to_add': 1, + 'priority': 80, }, - 'randomfallback': { - 'flavour': "sensible", # in pure, sensible, genre - 'track_to_add': "1", - } } # -class ConfMan(object): # CONFIG MANAGER CLASS +class ConfMan: # CONFIG MANAGER CLASS """ Configuration manager. Default configuration is stored in DEFAULT_CONF dictionnary. @@ -96,44 +128,66 @@ class ConfMan(object): # CONFIG MANAGER CLASS * command line options (overrides previous) """ - def __init__(self, logger, options=None): + def __init__(self, options=None): + self.log = logging.getLogger('sima') # options settings priority: - # defauts < conf. file < command line + # defauts < env. var. < conf. file < command line self.conf_file = options.get('conf_file') - self.config = None - self.defaults = dict(DEFAULT_CONF) + self.config = configparser.ConfigParser(inline_comment_prefixes='#') + self.config.read_dict(DEFAULT_CONF) + # update DEFAULT_CONF with env. var. + self.use_envar() self.startopt = options - ## Sima sqlite DB - self.db_file = None - self.log = logger - ## INIT CALLS - self.use_envar() + # INIT CALLS self.init_config() - self.control_conf() self.supersedes_config_with_cmd_line_options() - self.config['sima']['db_file'] = self.db_file + # set dbfile + self.config['sima']['db_file'] = join(self.config['sima']['var_dir'], 'sima.db') + # Controls files access + self.control_facc() - def get_pw(self): - try: - self.config.getboolean('MPD', 'password') - self.log.debug('No password set, proceeding without ' + - 'authentication...') - return None - except ValueError: - # ValueError if password not a boolean, hence an actual password. - pwd = self.config.get('MPD', 'password') - if not pwd: - self.log.debug('Password set as an empty string.') - return None - return pwd + # Create directories + data_dir = self.config['sima']['var_dir'] + if not isdir(data_dir): + self.log.trace('Creating "%s"', data_dir) + makedirs(data_dir) + chmod(data_dir, 0o700) + + def control_facc(self): + """Controls file access. + This is relevant only for file provided through the configuration file + since files provided on the command line are already checked with + argparse. Also add config['sima']['db_file'] contructed here in init + """ + ok = True + for op, ftochk in [('logfile', self.config.get('log', 'logfile')), + ('pidfile', self.config.get('daemon', 'pidfile')), + ('db file', self.config.get('sima', 'db_file'))]: + if not ftochk: + continue + if isdir(ftochk): + self.log.critical('Need a file not a directory: "%s"', ftochk) + ok = False + if not exists(ftochk): + # Is parent directory writable then + filedir = dirname(ftochk) + if not access(filedir, W_OK): + self.log.critical('no write access to "%s" (%s)', filedir, op) + ok = False + else: + if not access(ftochk, W_OK): + self.log.critical('no write access to "%s" (%s)', ftochk, op) + ok = False + if not ok: + sys.exit(2) def control_mod(self): """ Controls conf file permissions. """ mode = S_IMODE(stat(self.conf_file)[ST_MODE]) - self.log.debug('file permission is: %o' % mode) + self.log.debug('file permission is: %o', mode) if mode & S_IRWXO or mode & S_IRWXG: self.log.warning('File is readable by "other" and/or' + ' "group" (actual permission %o octal).' % @@ -147,55 +201,27 @@ class ConfMan(object): # CONFIG MANAGER CLASS for opt in self.config.options(sec): if opt in list(self.startopt.keys()): self.config.set(sec, opt, str(self.startopt.get(opt))) + # honor MPD_HOST format as in mpc(1) for command line option --host + if self.startopt.get('host'): + if '@' in self.startopt.get('host'): + host, passwd = utils.parse_mpd_host(self.startopt.get('host')) + if passwd: + self.config.set('MPD', 'password', passwd) + if host: + self.config.set('MPD', 'host', host) def use_envar(self): """Use MPD en.var. to set defaults""" mpd_host, mpd_port, passwd = utils.get_mpd_environ() if mpd_host: - self.log.info('Env. variable MPD_HOST set to "%s"' % mpd_host) - self.defaults['MPD']['host'] = mpd_host + self.log.info('Env. variable MPD_HOST set to "%s"', mpd_host) + self.config['MPD'].update(host=mpd_host) if passwd: self.log.info('Env. variable MPD_HOST contains password.') - self.defaults['MPD']['password'] = passwd + self.config['MPD'].update(password=passwd) if mpd_port: - self.log.info('Env. variable MPD_PORT set to "%s".' - % mpd_port) - self.defaults['MPD']['port'] = mpd_port - - def control_conf(self): - """Get through options/values and set defaults if not in conf file.""" - # Control presence of obsolete settings - for option in ['history', 'history_length', 'top_tracks']: - if self.config.has_option('sima', option): - self.log.warning('Obsolete setting found in conf file: "%s"' - % option) - # Setting default if not specified - for section in DEFAULT_CONF.keys(): - if section not in self.config.sections(): - self.log.debug('[%s] NOT in conf file' % section) - self.config.add_section(section) - for option in self.defaults[section]: - self.config.set(section, - option, - self.defaults[section][option]) - self.log.debug( - 'Setting option with default value: %s = %s' % - (option, self.defaults[section][option])) - elif section in self.config.sections(): - self.log.debug('[%s] present in conf file' % section) - for option in self.defaults[section]: - if self.config.has_option(section, option): - #self.log.debug(u'option "%s" set to "%s" in conf. file' % - # (option, self.config.get(section, option))) - pass - else: - self.log.debug( - 'Option "%s" missing in section "%s"' % - (option, section)) - self.log.debug('=> setting default "%s" (may not suit you…)' % - self.defaults[section][option]) - self.config.set(section, option, - self.defaults[section][option]) + self.log.info('Env. variable MPD_PORT set to "%s".', mpd_port) + self.config['MPD'].update(port=mpd_port) def init_config(self): """ @@ -204,59 +230,46 @@ class ConfMan(object): # CONFIG MANAGER CLASS http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html """ - homedir = environ.get('HOME') + homedir = getenv('HOME') - if environ.get('XDG_DATA_HOME'): - data_dir = join(environ.get('XDG_DATA_HOME'), DIRNAME) - elif self.startopt.get('var_dir'): - # If var folder is provided via CLI set data_dir accordingly - data_dir = join(self.startopt.get('var_dir')) - elif (homedir and isdir(homedir) and homedir not in ['/']): + if getenv('XDG_DATA_HOME'): + data_dir = join(getenv('XDG_DATA_HOME'), DIRNAME) + elif homedir and isdir(homedir) and homedir not in ['/']: data_dir = join(homedir, '.local', 'share', DIRNAME) else: - self.log.error('Can\'t find a suitable location for data folder (XDG_DATA_HOME)') - self.log.error('Please use "--var_dir" to set a proper location') + self.log.critical('Can\'t find a suitable location for data folder (XDG_DATA_HOME)') + self.log.critical('Please use "--var-dir" to set a proper location') sys.exit(1) - if not isdir(data_dir): - makedirs(data_dir) - chmod(data_dir, 0o700) - if self.startopt.get('conf_file'): # No need to handle conf file location pass - elif environ.get('XDG_CONFIG_HOME'): - conf_dir = join(environ.get('XDG_CONFIG_HOME'), DIRNAME) - elif (homedir and isdir(homedir) and homedir not in ['/']): + elif getenv('XDG_CONFIG_HOME'): + conf_dir = join(getenv('XDG_CONFIG_HOME'), DIRNAME) + self.conf_file = join(conf_dir, CONF_FILE) + elif homedir and isdir(homedir) and homedir not in ['/']: conf_dir = join(homedir, '.config', DIRNAME) - # Create conf_dir if necessary - if not isdir(conf_dir): - makedirs(conf_dir) - chmod(conf_dir, 0o700) self.conf_file = join(conf_dir, CONF_FILE) else: - self.log.error('Can\'t find a suitable location for config folder (XDG_CONFIG_HOME)') - self.log.error('Please use "--config" to locate the conf file') + self.log.critical('Can\'t find a suitable location for config folder (XDG_CONFIG_HOME)') + self.log.critical('Please use "--config" to locate the conf file') sys.exit(1) - self.db_file = join(data_dir, 'sima.db') + # Sima sqlite DB + self.config['sima']['var_dir'] = join(data_dir) - config = configparser.SafeConfigParser() # If no conf file present, uses defaults if not isfile(self.conf_file): - self.config = config return - self.log.info('Loading configuration from: %s' % self.conf_file) + self.log.info('Loading configuration from: %s', self.conf_file) self.control_mod() try: - config.read(self.conf_file) + self.config.read(self.conf_file) except Error as err: self.log.error(err) sys.exit(1) - self.config = config - # VIM MODLINE # vim: ai ts=4 sw=4 sts=4 expandtab