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