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