1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2009-2015, 2019-2020 kaliko <kaliko@azylum.org>
3 # Copyright (c) 2019 sacha <sachahony@gmail.com>
5 # This file is part of sima
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.
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.
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/>.
20 # pylint: disable=bad-continuation
23 Deal with configuration and data files.
24 Parse configuration file and set defaults for missing options.
32 from configparser import Error
33 from os import (access, makedirs, environ, stat, chmod, W_OK)
34 from os.path import (join, isdir, isfile, dirname, exists)
35 from stat import (S_IMODE, ST_MODE, S_IRWXO, S_IRWXG)
41 CONF_FILE = 'mpd_sima.cfg'
50 'internal': "Crop, Lastfm, Random",
53 'history_duration': 8,
56 'musicbrainzid': "true",
57 'repeat_disable_queue': "true",
58 'single_disable_queue': "true",
59 'mopidy_compat': "false",
74 'queue_mode': "track", # TODO control values
76 'single_album': "false",
79 'shuffle_album': False,
80 'track_to_add_from_album': 0, # <=0 means keep all
86 'flavour': "sensible", # in pure, sensible
97 'queue_mode': "track",
106 class ConfMan: # CONFIG MANAGER CLASS
108 Configuration manager.
109 Default configuration is stored in DEFAULT_CONF dictionnary.
110 First init_config() run to get config from file.
111 Then control_conf() is run and retrieve configuration from defaults if not
113 These settings are then updated with command line options with
114 supersedes_config_with_cmd_line_options().
116 Order of priority for the origin of an option is then (lowest to highest):
118 * Env. Var for MPD host, port and password
119 * configuration file (overrides previous)
120 * command line options (overrides previous)
123 def __init__(self, options=None):
124 self.log = logging.getLogger('sima')
125 # options settings priority:
126 # defauts < env. var. < conf. file < command line
127 self.conf_file = options.get('conf_file')
128 self.config = configparser.ConfigParser(inline_comment_prefixes='#')
129 self.config.read_dict(DEFAULT_CONF)
130 # update DEFAULT_CONF with env. var.
132 self.startopt = options
136 self.supersedes_config_with_cmd_line_options()
137 # Controls files access
140 self.config['sima']['db_file'] = join(self.config['sima']['var_dir'], 'sima.db')
143 data_dir = self.config['sima']['var_dir']
144 if not isdir(data_dir):
145 self.log.trace('Creating "%s"', data_dir)
147 chmod(data_dir, 0o700)
149 def control_facc(self):
150 """Controls file access.
151 This is relevant only for file provided through the configuration file
152 since files provided on the command line are already checked with
156 for op, ftochk in [('logfile', self.config.get('log', 'logfile')),
157 ('pidfile', self.config.get('daemon', 'pidfile')),]:
161 self.log.critical('Need a file not a directory: "%s"', ftochk)
163 if not exists(ftochk):
164 # Is parent directory writable then
165 filedir = dirname(ftochk)
166 if not access(filedir, W_OK):
167 self.log.critical('no write access to "%s" (%s)', filedir, op)
170 if not access(ftochk, W_OK):
171 self.log.critical('no write access to "%s" (%s)', ftochk, op)
174 if exists(self.conf_file):
175 self.log.warning('Try to check the configuration file: %s', self.conf_file)
178 def control_mod(self):
180 Controls conf file permissions.
182 mode = S_IMODE(stat(self.conf_file)[ST_MODE])
183 self.log.debug('file permission is: %o', mode)
184 if mode & S_IRWXO or mode & S_IRWXG:
185 self.log.warning('File is readable by "other" and/or' +
186 ' "group" (actual permission %o octal).' %
188 self.log.warning('Consider setting permissions' +
191 def supersedes_config_with_cmd_line_options(self):
192 """Updates defaults settings with command line options"""
193 for sec in self.config.sections():
194 for opt in self.config.options(sec):
195 if opt in list(self.startopt.keys()):
196 self.config.set(sec, opt, str(self.startopt.get(opt)))
197 # honor MPD_HOST format as in mpc(1) for command line option --host
198 if self.startopt.get('host'):
199 if '@' in self.startopt.get('host'):
200 passwd, host = self.startopt.get('host').split('@')
201 self.config.set('MPD', 'password', passwd)
202 self.config.set('MPD', 'host', host)
205 """Use MPD en.var. to set defaults"""
206 mpd_host, mpd_port, passwd = utils.get_mpd_environ()
208 self.log.info('Env. variable MPD_HOST set to "%s"', mpd_host)
209 self.config['MPD'].update(host=mpd_host)
211 self.log.info('Env. variable MPD_HOST contains password.')
212 self.config['MPD'].update(password=passwd)
214 self.log.info('Env. variable MPD_PORT set to "%s".', mpd_port)
215 self.config['MPD'].update(port=mpd_port)
217 def init_config(self):
219 Use XDG directory standard if exists
220 else use "HOME/(.config|.local/share)/sima/"
221 http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
224 homedir = environ.get('HOME')
226 if environ.get('XDG_DATA_HOME'):
227 data_dir = join(environ.get('XDG_DATA_HOME'), DIRNAME)
228 elif homedir and isdir(homedir) and homedir not in ['/']:
229 data_dir = join(homedir, '.local', 'share', DIRNAME)
231 self.log.critical('Can\'t find a suitable location for data folder (XDG_DATA_HOME)')
232 self.log.critical('Please use "--var-dir" to set a proper location')
235 if self.startopt.get('conf_file'):
236 # No need to handle conf file location
238 elif environ.get('XDG_CONFIG_HOME'):
239 conf_dir = join(environ.get('XDG_CONFIG_HOME'), DIRNAME)
240 elif homedir and isdir(homedir) and homedir not in ['/']:
241 conf_dir = join(homedir, '.config', DIRNAME)
242 self.conf_file = join(conf_dir, CONF_FILE)
244 self.log.critical('Can\'t find a suitable location for config folder (XDG_CONFIG_HOME)')
245 self.log.critical('Please use "--config" to locate the conf file')
249 self.config['sima']['var_dir'] = join(data_dir)
251 # If no conf file present, uses defaults
252 if not isfile(self.conf_file):
255 self.log.info('Loading configuration from: %s', self.conf_file)
259 self.config.read(self.conf_file)
265 # vim: ai ts=4 sw=4 sts=4 expandtab