]> kaliko git repositories - mpd-sima.git/blob - sima/utils/config.py
Add missing default config options for Tags
[mpd-sima.git] / sima / utils / config.py
1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2009-2015, 2019-2020 kaliko <kaliko@azylum.org>
3 # Copyright (c) 2019 sacha <sachahony@gmail.com>
4 #
5 #  This file is part of sima
6 #
7 #  sima is free software: you can redistribute it and/or modify
8 #  it under the terms of the GNU General Public License as published by
9 #  the Free Software Foundation, either version 3 of the License, or
10 #  (at your option) any later version.
11 #
12 #  sima is distributed in the hope that it will be useful,
13 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #  GNU General Public License for more details.
16 #
17 #  You should have received a copy of the GNU General Public License
18 #  along with sima.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 # pylint: disable=bad-continuation
21
22 """
23 Deal with configuration and data files.
24 Parse configuration file and set defaults for missing options.
25 """
26
27 # IMPORTS
28 import configparser
29 import logging
30 import sys
31
32 from configparser import Error
33 from os import (access, makedirs, environ, stat, chmod, W_OK)
34 from os.path import (join, isdir, isfile, dirname, exists)
35 from stat import (S_IMODE, ST_MODE, S_IRWXO, S_IRWXG)
36
37 from . import utils
38
39 # DEFAULTS
40 DIRNAME = 'mpd_sima'
41 CONF_FILE = 'mpd_sima.cfg'
42
43 DEFAULT_CONF = {
44         'MPD': {
45             'host': "localhost",
46             #'password': "",
47             'port': 6600,
48             },
49         'sima': {
50             'internal': "Crop, Lastfm, Random",
51             'contrib': "",
52             'user_db': "false",
53             'history_duration': 8,
54             'queue_length': 2,
55             'var_dir': 'empty',
56             'musicbrainzid': "true",
57             'repeat_disable_queue': "true",
58             'single_disable_queue': "true",
59             'mopidy_compat': "false",
60             },
61         'daemon': {
62             'daemon': False,
63             'pidfile': "",
64             },
65         'log': {
66             'verbosity': "info",
67             'logfile': "",
68             },
69         'crop': {
70             'consume': 10,
71             'priority': 0,
72             },
73         'lastfm': {
74             'queue_mode': "track",  # TODO control values
75             'max_art': 10,
76             'single_album': "false",
77             'track_to_add': 1,
78             'album_to_add': 1,
79             'shuffle_album': False,
80             'track_to_add_from_album': 0,  # <=0 means keep all
81             'depth': 1,
82             'cache': True,
83             'priority': 100,
84             },
85         'random': {
86             'flavour': "sensible",  # in pure, sensible
87             'track_to_add': 1,
88             'priority': 50,
89             },
90         'tags': {
91             'comment': "",
92             'date': "",
93             'genre': "",
94             'label': "",
95             'originaldate': "",
96             'filter': "",
97             'track_to_add': 1,
98             'priority': 80,
99             }
100         }
101 #
102
103
104 class ConfMan:  # CONFIG MANAGER CLASS
105     """
106     Configuration manager.
107     Default configuration is stored in DEFAULT_CONF dictionnary.
108     First init_config() run to get config from file.
109     Then control_conf() is run and retrieve configuration from defaults if not
110     set in conf files.
111     These settings are then updated with command line options with
112     supersedes_config_with_cmd_line_options().
113
114     Order of priority for the origin of an option is then (lowest to highest):
115         * DEFAULT_CONF
116         * Env. Var for MPD host, port and password
117         * configuration file (overrides previous)
118         * command line options (overrides previous)
119     """
120
121     def __init__(self, options=None):
122         self.log = logging.getLogger('sima')
123         # options settings priority:
124         # defauts < env. var. < conf. file < command line
125         self.conf_file = options.get('conf_file')
126         self.config = configparser.ConfigParser(inline_comment_prefixes='#')
127         self.config.read_dict(DEFAULT_CONF)
128         # update DEFAULT_CONF with env. var.
129         self.use_envar()
130         self.startopt = options
131
132         ## INIT CALLS
133         self.init_config()
134         self.supersedes_config_with_cmd_line_options()
135         # Controls files access
136         self.control_facc()
137         # set dbfile
138         self.config['sima']['db_file'] = join(self.config['sima']['var_dir'], 'sima.db')
139
140         # Create directories
141         data_dir = self.config['sima']['var_dir']
142         if not isdir(data_dir):
143             self.log.trace('Creating "%s"', data_dir)
144             makedirs(data_dir)
145             chmod(data_dir, 0o700)
146
147     def control_facc(self):
148         """Controls file access.
149         This is relevant only for file provided through the configuration file
150         since files provided on the command line are already checked with
151         argparse.
152         """
153         ok = True
154         for op, ftochk in [('logfile', self.config.get('log', 'logfile')),
155                            ('pidfile', self.config.get('daemon', 'pidfile')),]:
156             if not ftochk:
157                 continue
158             if isdir(ftochk):
159                 self.log.critical('Need a file not a directory: "%s"', ftochk)
160                 ok = False
161             if not exists(ftochk):
162                 # Is parent directory writable then
163                 filedir = dirname(ftochk)
164                 if not access(filedir, W_OK):
165                     self.log.critical('no write access to "%s" (%s)', filedir, op)
166                     ok = False
167             else:
168                 if not access(ftochk, W_OK):
169                     self.log.critical('no write access to "%s" (%s)', ftochk, op)
170                     ok = False
171         if not ok:
172             if exists(self.conf_file):
173                 self.log.warning('Try to check the configuration file: %s', self.conf_file)
174             sys.exit(2)
175
176     def control_mod(self):
177         """
178         Controls conf file permissions.
179         """
180         mode = S_IMODE(stat(self.conf_file)[ST_MODE])
181         self.log.debug('file permission is: %o', mode)
182         if mode & S_IRWXO or mode & S_IRWXG:
183             self.log.warning('File is readable by "other" and/or' +
184                              ' "group" (actual permission %o octal).' %
185                              mode)
186             self.log.warning('Consider setting permissions' +
187                              ' to 600 octal.')
188
189     def supersedes_config_with_cmd_line_options(self):
190         """Updates defaults settings with command line options"""
191         for sec in self.config.sections():
192             for opt in self.config.options(sec):
193                 if opt in list(self.startopt.keys()):
194                     self.config.set(sec, opt, str(self.startopt.get(opt)))
195         # honor MPD_HOST format as in mpc(1)  for command line option --host
196         if self.startopt.get('host'):
197             if '@' in self.startopt.get('host'):
198                 passwd, host = self.startopt.get('host').split('@')
199                 self.config.set('MPD', 'password', passwd)
200                 self.config.set('MPD', 'host', host)
201
202     def use_envar(self):
203         """Use MPD en.var. to set defaults"""
204         mpd_host, mpd_port, passwd = utils.get_mpd_environ()
205         if mpd_host:
206             self.log.info('Env. variable MPD_HOST set to "%s"', mpd_host)
207             self.config['MPD'].update(host=mpd_host)
208         if passwd:
209             self.log.info('Env. variable MPD_HOST contains password.')
210             self.config['MPD'].update(password=passwd)
211         if mpd_port:
212             self.log.info('Env. variable MPD_PORT set to "%s".', mpd_port)
213             self.config['MPD'].update(port=mpd_port)
214
215     def init_config(self):
216         """
217         Use XDG directory standard if exists
218         else use "HOME/(.config|.local/share)/sima/"
219         http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
220         """
221
222         homedir = environ.get('HOME')
223
224         if environ.get('XDG_DATA_HOME'):
225             data_dir = join(environ.get('XDG_DATA_HOME'), DIRNAME)
226         elif homedir and isdir(homedir) and homedir not in ['/']:
227             data_dir = join(homedir, '.local', 'share', DIRNAME)
228         else:
229             self.log.critical('Can\'t find a suitable location for data folder (XDG_DATA_HOME)')
230             self.log.critical('Please use "--var-dir" to set a proper location')
231             sys.exit(1)
232
233         if self.startopt.get('conf_file'):
234             # No need to handle conf file location
235             pass
236         elif environ.get('XDG_CONFIG_HOME'):
237             conf_dir = join(environ.get('XDG_CONFIG_HOME'), DIRNAME)
238         elif homedir and isdir(homedir) and homedir not in ['/']:
239             conf_dir = join(homedir, '.config', DIRNAME)
240             self.conf_file = join(conf_dir, CONF_FILE)
241         else:
242             self.log.critical('Can\'t find a suitable location for config folder (XDG_CONFIG_HOME)')
243             self.log.critical('Please use "--config" to locate the conf file')
244             sys.exit(1)
245
246         ## Sima sqlite DB
247         self.config['sima']['var_dir'] = join(data_dir)
248
249         # If no conf file present, uses defaults
250         if not isfile(self.conf_file):
251             return
252
253         self.log.info('Loading configuration from:  %s', self.conf_file)
254         self.control_mod()
255
256         try:
257             self.config.read(self.conf_file)
258         except Error as err:
259             self.log.error(err)
260             sys.exit(1)
261
262 # VIM MODLINE
263 # vim: ai ts=4 sw=4 sts=4 expandtab