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