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