]> kaliko git repositories - mpd-sima.git/blob - sima/utils/config.py
Integrate dynamic option in echonest plugin
[mpd-sima.git] / sima / utils / config.py
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009, 2010, 2011, 2013 Jack Kaliko <kaliko@azylum.org>
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 #
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 sys
30
31 from configparser import Error
32 from os import (makedirs, environ, stat, chmod)
33 from os.path import (join, isdir, isfile)
34 from stat import (S_IMODE, ST_MODE, S_IRWXO, S_IRWXG)
35
36 from . import utils
37
38 # DEFAULTS
39 DIRNAME = 'mpd_sima'
40 CONF_FILE = 'sima.cfg'
41
42 DEFAULT_CONF = {
43         'MPD': {
44             'host': "localhost",
45             #'password': "",
46             'port': "6600",
47             },
48         'sima': {
49             'internal': "Crop, Lastfm, RandomFallBack",
50             'contrib': "",
51             'user_db': "false",
52             'history_duration': "8",
53             'queue_length': "1",
54             },
55         'daemon':{
56             'daemon': "false",
57             'pidfile': "",
58             },
59         'log': {
60             'verbosity': "info",
61             'logfile': "",
62             },
63         'echonest': {
64             'queue_mode': "track", #TODO control values
65             'dynamic': "15",
66             'single_album': "false",
67             'track_to_add': "1",
68             'album_to_add': "1",
69             'depth': "1",
70             },
71         'lastfm': {
72             'dynamic': "10",
73             'similarity': "15",
74             'queue_mode': "track", #TODO control values
75             'single_album': "false",
76             'track_to_add': "1",
77             'album_to_add': "1",
78             'depth': "1",
79             },
80         'randomfallback': {
81             'flavour': "sensible", # in pure, sensible, genre
82             'track_to_add': "1",
83             }
84         }
85 #
86
87
88 class ConfMan(object):  # CONFIG MANAGER CLASS
89     """
90     Configuration manager.
91     Default configuration is stored in DEFAULT_CONF dictionnary.
92     First init_config() run to get config from file.
93     Then control_conf() is run and retrieve configuration from defaults if not
94     set in conf files.
95     These settings are then updated with command line options with
96     supersedes_config_with_cmd_line_options().
97
98     Order of priority for the origin of an option is then (lowest to highest):
99         * DEFAULT_CONF
100         * Env. Var for MPD host, port and password
101         * configuration file (overrides previous)
102         * command line options (overrides previous)
103     """
104
105     def __init__(self, logger, options=None):
106         # options settings priority:
107         # defauts < conf. file < command line
108         self.conf_file = options.get('conf_file')
109         self.config = None
110         self.defaults = dict(DEFAULT_CONF)
111         self.startopt = options
112         ## Sima sqlite DB
113         self.db_file = None
114
115         self.log = logger
116         ## INIT CALLS
117         self.use_envar()
118         self.init_config()
119         self.control_conf()
120         self.supersedes_config_with_cmd_line_options()
121         self.config['sima']['db_file'] = self.db_file
122
123     def get_pw(self):
124         try:
125             self.config.getboolean('MPD', 'password')
126             self.log.debug('No password set, proceeding without ' +
127                            'authentication...')
128             return None
129         except ValueError:
130             # ValueError if password not a boolean, hence an actual password.
131             pwd = self.config.get('MPD', 'password')
132             if not pwd:
133                 self.log.debug('Password set as an empty string.')
134                 return None
135             return pwd
136
137     def control_mod(self):
138         """
139         Controls conf file permissions.
140         """
141         mode = S_IMODE(stat(self.conf_file)[ST_MODE])
142         self.log.debug('file permission is: %o' % mode)
143         if mode & S_IRWXO or mode & S_IRWXG:
144             self.log.warning('File is readable by "other" and/or' +
145                              ' "group" (actual permission %o octal).' %
146                              mode)
147             self.log.warning('Consider setting permissions' +
148                              ' to 600 octal.')
149
150     def supersedes_config_with_cmd_line_options(self):
151         """Updates defaults settings with command line options"""
152         for sec in self.config.sections():
153             for opt in self.config.options(sec):
154                 if opt in list(self.startopt.keys()):
155                     self.config.set(sec, opt, str(self.startopt.get(opt)))
156
157     def use_envar(self):
158         """Use MPD en.var. to set defaults"""
159         mpd_host, mpd_port, passwd = utils.get_mpd_environ()
160         if mpd_host:
161             self.log.info('Env. variable MPD_HOST set to "%s"' % mpd_host)
162             self.defaults['MPD']['host'] = mpd_host
163         if passwd:
164             self.log.info('Env. variable MPD_HOST contains password.')
165             self.defaults['MPD']['password'] = passwd
166         if mpd_port:
167             self.log.info('Env. variable MPD_PORT set to "%s".'
168                                   % mpd_port)
169             self.defaults['MPD']['port'] = mpd_port
170
171     def control_conf(self):
172         """Get through options/values and set defaults if not in conf file."""
173         # Control presence of obsolete settings
174         for option in ['history', 'history_length', 'top_tracks']:
175             if self.config.has_option('sima', option):
176                 self.log.warning('Obsolete setting found in conf file: "%s"'
177                         % option)
178         # Setting default if not specified
179         for section in DEFAULT_CONF.keys():
180             if section not in self.config.sections():
181                 self.log.debug('[%s] NOT in conf file' % section)
182                 self.config.add_section(section)
183                 for option in self.defaults[section]:
184                     self.config.set(section,
185                             option,
186                             self.defaults[section][option])
187                     self.log.debug(
188                             'Setting option with default value: %s = %s' %
189                             (option, self.defaults[section][option]))
190             elif section in self.config.sections():
191                 self.log.debug('[%s] present in conf file' % section)
192                 for option in self.defaults[section]:
193                     if self.config.has_option(section, option):
194                         #self.log.debug(u'option "%s" set to "%s" in conf. file' %
195                         #              (option, self.config.get(section, option)))
196                         pass
197                     else:
198                         self.log.debug(
199                                 'Option "%s" missing in section "%s"' %
200                                 (option, section))
201                         self.log.debug('=> setting default "%s" (may not suit you…)' %
202                                        self.defaults[section][option])
203                         self.config.set(section, option,
204                                         self.defaults[section][option])
205
206     def init_config(self):
207         """
208         Use XDG directory standard if exists
209         else use "HOME/(.config|.local/share)/sima/"
210         http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
211         """
212
213         homedir = environ.get('HOME')
214
215         if environ.get('XDG_DATA_HOME'):
216             data_dir = join(environ.get('XDG_DATA_HOME'), DIRNAME)
217         elif self.startopt.get('var_dir'):
218             # If var folder is provided via CLI set data_dir accordingly
219             data_dir = join(self.startopt.get('var_dir'))
220         elif (homedir and isdir(homedir) and homedir not in ['/']):
221             data_dir = join(homedir, '.local', 'share', DIRNAME)
222         else:
223             self.log.error('Can\'t find a suitable location for data folder (XDG_DATA_HOME)')
224             self.log.error('Please use "--var_dir" to set a proper location')
225             sys.exit(1)
226
227         if not isdir(data_dir):
228             makedirs(data_dir)
229             chmod(data_dir, 0o700)
230
231         if self.startopt.get('conf_file'):
232             # No need to handle conf file location
233             pass
234         elif environ.get('XDG_CONFIG_HOME'):
235             conf_dir = join(environ.get('XDG_CONFIG_HOME'), DIRNAME)
236         elif (homedir and isdir(homedir) and homedir not in ['/']):
237             conf_dir = join(homedir, '.config', DIRNAME)
238             # Create conf_dir if necessary
239             if not isdir(conf_dir):
240                 makedirs(conf_dir)
241                 chmod(conf_dir, 0o700)
242             self.conf_file = join(conf_dir, CONF_FILE)
243         else:
244             self.log.error('Can\'t find a suitable location for config folder (XDG_CONFIG_HOME)')
245             self.log.error('Please use "--config" to locate the conf file')
246             sys.exit(1)
247
248         self.db_file = join(data_dir, 'sima.db')
249
250         config = configparser.SafeConfigParser()
251         # If no conf file present, uses defaults
252         if not isfile(self.conf_file):
253             self.config = config
254             return
255
256         self.log.info('Loading configuration from:  %s' % self.conf_file)
257         self.control_mod()
258
259         try:
260             config.read(self.conf_file)
261         except Error as err:
262             self.log.error(err)
263             sys.exit(1)
264
265         self.config = config
266
267 # VIM MODLINE
268 # vim: ai ts=4 sw=4 sts=4 expandtab