]> kaliko git repositories - mpd-sima.git/blobdiff - sima/utils/config.py
Add missing default config options for Tags
[mpd-sima.git] / sima / utils / config.py
index 99fb3227b7b69b18f085d513dcefcb99e608c67d..182b036593a58ada977596653ef7569c20a0fd80 100644 (file)
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
-
-# Copyright (c) 2009, 2010, 2011, 2013 Jack Kaliko <kaliko@azylum.org>
+# Copyright (c) 2009-2015, 2019-2020 kaliko <kaliko@azylum.org>
+# Copyright (c) 2019 sacha <sachahony@gmail.com>
 #
 #  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 <http://www.gnu.org/licenses/>.
 #
-#
+# pylint: disable=bad-continuation
 
 """
 Deal with configuration and data files.
@@ -26,65 +26,82 @@ 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, environ, 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 = 'mpd_sima'
-CONF_FILE = 'sima.cfg'
+CONF_FILE = 'mpd_sima.cfg'
 
 DEFAULT_CONF = {
         'MPD': {
             'host': "localhost",
             #'password': "",
-            'port': "6600",
+            'port': 6600,
             },
         'sima': {
-            'internal': "Crop, Lastfm, RandomFallBack",
+            'internal': "Crop, Lastfm, Random",
             'contrib': "",
             'user_db': "false",
-            'history_duration': "8",
-            'queue_length': "1",
+            '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': {
-            'queue_mode': "track", #TODO control values
-            'max_art': "15",
-            'single_album': "false",
-            'track_to_add': "1",
-            'album_to_add': "1",
-            'depth': "1",
+        'crop': {
+            'consume': 10,
+            'priority': 0,
             },
         'lastfm': {
-            'queue_mode': "track", #TODO control values
-            'max_art': "10",
+            'queue_mode': "track",  # TODO control values
+            'max_art': 10,
             'single_album': "false",
-            'track_to_add': "1",
-            'album_to_add': "1",
-            'depth': "1",
+            '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,
             },
-        'randomfallback': {
-            'flavour': "sensible", # in pure, sensible, genre
-            'track_to_add': "1",
+        'random': {
+            'flavour': "sensible",  # in pure, sensible
+            'track_to_add': 1,
+            'priority': 50,
+            },
+        'tags': {
+            'comment': "",
+            'date': "",
+            'genre': "",
+            'label': "",
+            'originaldate': "",
+            'filter': "",
+            'track_to_add': 1,
+            'priority': 80,
             }
         }
 #
 
 
-class ConfMan(object):  # CONFIG MANAGER CLASS
+class ConfMan:  # CONFIG MANAGER CLASS
     """
     Configuration manager.
     Default configuration is stored in DEFAULT_CONF dictionnary.
@@ -101,44 +118,67 @@ 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()
         self.init_config()
-        self.control_conf()
         self.supersedes_config_with_cmd_line_options()
-        self.config['sima']['db_file'] = self.db_file
+        # Controls files access
+        self.control_facc()
+        # set dbfile
+        self.config['sima']['db_file'] = join(self.config['sima']['var_dir'], 'sima.db')
 
-    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.
+        """
+        ok = True
+        for op, ftochk in [('logfile', self.config.get('log', 'logfile')),
+                           ('pidfile', self.config.get('daemon', 'pidfile')),]:
+            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:
+            if exists(self.conf_file):
+                self.log.warning('Try to check the configuration file: %s', self.conf_file)
+            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).' %
@@ -152,55 +192,25 @@ 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'):
+                passwd, host = self.startopt.get('host').split('@')
+                self.config.set('MPD', 'password', passwd)
+                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):
         """
@@ -213,55 +223,41 @@ class ConfMan(object):  # CONFIG MANAGER CLASS
 
         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 ['/']):
+        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 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