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