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