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