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