1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2009-2015, 2019-2021 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",
98 'single_album': False,
104 'queue_mode': "track",
105 'single_album': False,
114 class ConfMan: # CONFIG MANAGER CLASS
116 Configuration manager.
117 Default configuration is stored in DEFAULT_CONF dictionnary.
118 First init_config() run to get config from file.
119 Then control_conf() is run and retrieve configuration from defaults if not
121 These settings are then updated with command line options with
122 supersedes_config_with_cmd_line_options().
124 Order of priority for the origin of an option is then (lowest to highest):
126 * Env. Var for MPD host, port and password
127 * configuration file (overrides previous)
128 * command line options (overrides previous)
131 def __init__(self, options=None):
132 self.log = logging.getLogger('sima')
133 # options settings priority:
134 # defauts < env. var. < conf. file < command line
135 self.conf_file = options.get('conf_file')
136 self.config = configparser.ConfigParser(inline_comment_prefixes='#')
137 self.config.read_dict(DEFAULT_CONF)
138 # update DEFAULT_CONF with env. var.
140 self.startopt = options
144 self.supersedes_config_with_cmd_line_options()
146 self.config['sima']['db_file'] = join(self.config['sima']['var_dir'], 'sima.db')
147 # Controls files access
151 data_dir = self.config['sima']['var_dir']
152 if not isdir(data_dir):
153 self.log.trace('Creating "%s"', data_dir)
155 chmod(data_dir, 0o700)
157 def control_facc(self):
158 """Controls file access.
159 This is relevant only for file provided through the configuration file
160 since files provided on the command line are already checked with
161 argparse. Also add config['sima']['db_file'] contructed here in init
164 for op, ftochk in [('logfile', self.config.get('log', 'logfile')),
165 ('pidfile', self.config.get('daemon', 'pidfile')),
166 ('db file', self.config.get('sima', 'db_file'))]:
170 self.log.critical('Need a file not a directory: "%s"', ftochk)
172 if not exists(ftochk):
173 # Is parent directory writable then
174 filedir = dirname(ftochk)
175 if not access(filedir, W_OK):
176 self.log.critical('no write access to "%s" (%s)', filedir, op)
179 if not access(ftochk, W_OK):
180 self.log.critical('no write access to "%s" (%s)', ftochk, op)
185 def control_mod(self):
187 Controls conf file permissions.
189 mode = S_IMODE(stat(self.conf_file)[ST_MODE])
190 self.log.debug('file permission is: %o', mode)
191 if mode & S_IRWXO or mode & S_IRWXG:
192 self.log.warning('File is readable by "other" and/or' +
193 ' "group" (actual permission %o octal).' %
195 self.log.warning('Consider setting permissions' +
198 def supersedes_config_with_cmd_line_options(self):
199 """Updates defaults settings with command line options"""
200 for sec in self.config.sections():
201 for opt in self.config.options(sec):
202 if opt in list(self.startopt.keys()):
203 self.config.set(sec, opt, str(self.startopt.get(opt)))
204 # honor MPD_HOST format as in mpc(1) for command line option --host
205 if self.startopt.get('host'):
206 if '@' in self.startopt.get('host'):
207 passwd, host = self.startopt.get('host').split('@')
208 self.config.set('MPD', 'password', passwd)
209 self.config.set('MPD', 'host', host)
212 """Use MPD en.var. to set defaults"""
213 mpd_host, mpd_port, passwd = utils.get_mpd_environ()
215 self.log.info('Env. variable MPD_HOST set to "%s"', mpd_host)
216 self.config['MPD'].update(host=mpd_host)
218 self.log.info('Env. variable MPD_HOST contains password.')
219 self.config['MPD'].update(password=passwd)
221 self.log.info('Env. variable MPD_PORT set to "%s".', mpd_port)
222 self.config['MPD'].update(port=mpd_port)
224 def init_config(self):
226 Use XDG directory standard if exists
227 else use "HOME/(.config|.local/share)/sima/"
228 http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
231 homedir = environ.get('HOME')
233 if environ.get('XDG_DATA_HOME'):
234 data_dir = join(environ.get('XDG_DATA_HOME'), DIRNAME)
235 elif homedir and isdir(homedir) and homedir not in ['/']:
236 data_dir = join(homedir, '.local', 'share', DIRNAME)
238 self.log.critical('Can\'t find a suitable location for data folder (XDG_DATA_HOME)')
239 self.log.critical('Please use "--var-dir" to set a proper location')
242 if self.startopt.get('conf_file'):
243 # No need to handle conf file location
245 elif environ.get('XDG_CONFIG_HOME'):
246 conf_dir = join(environ.get('XDG_CONFIG_HOME'), DIRNAME)
247 self.conf_file = join(conf_dir, CONF_FILE)
248 elif homedir and isdir(homedir) and homedir not in ['/']:
249 conf_dir = join(homedir, '.config', DIRNAME)
250 self.conf_file = join(conf_dir, CONF_FILE)
252 self.log.critical('Can\'t find a suitable location for config folder (XDG_CONFIG_HOME)')
253 self.log.critical('Please use "--config" to locate the conf file')
257 self.config['sima']['var_dir'] = join(data_dir)
259 # If no conf file present, uses defaults
260 if not isfile(self.conf_file):
263 self.log.info('Loading configuration from: %s', self.conf_file)
267 self.config.read(self.conf_file)
273 # vim: ai ts=4 sw=4 sts=4 expandtab