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