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
104 class ConfMan: # CONFIG MANAGER CLASS
106 Configuration manager.
107 Default configuration is stored in DEFAULT_CONF dictionnary.
108 First init_config() run to get config from file.
109 Then control_conf() is run and retrieve configuration from defaults if not
111 These settings are then updated with command line options with
112 supersedes_config_with_cmd_line_options().
114 Order of priority for the origin of an option is then (lowest to highest):
116 * Env. Var for MPD host, port and password
117 * configuration file (overrides previous)
118 * command line options (overrides previous)
121 def __init__(self, options=None):
122 self.log = logging.getLogger('sima')
123 # options settings priority:
124 # defauts < env. var. < conf. file < command line
125 self.conf_file = options.get('conf_file')
126 self.config = configparser.ConfigParser(inline_comment_prefixes='#')
127 self.config.read_dict(DEFAULT_CONF)
128 # update DEFAULT_CONF with env. var.
130 self.startopt = options
134 self.supersedes_config_with_cmd_line_options()
135 # Controls files access
138 self.config['sima']['db_file'] = join(self.config['sima']['var_dir'], 'sima.db')
141 data_dir = self.config['sima']['var_dir']
142 if not isdir(data_dir):
143 self.log.trace('Creating "%s"', data_dir)
145 chmod(data_dir, 0o700)
147 def control_facc(self):
148 """Controls file access.
149 This is relevant only for file provided through the configuration file
150 since files provided on the command line are already checked with
154 for op, ftochk in [('logfile', self.config.get('log', 'logfile')),
155 ('pidfile', self.config.get('daemon', 'pidfile')),]:
159 self.log.critical('Need a file not a directory: "%s"', ftochk)
161 if not exists(ftochk):
162 # Is parent directory writable then
163 filedir = dirname(ftochk)
164 if not access(filedir, W_OK):
165 self.log.critical('no write access to "%s" (%s)', filedir, op)
168 if not access(ftochk, W_OK):
169 self.log.critical('no write access to "%s" (%s)', ftochk, op)
172 if exists(self.conf_file):
173 self.log.warning('Try to check the configuration file: %s', self.conf_file)
176 def control_mod(self):
178 Controls conf file permissions.
180 mode = S_IMODE(stat(self.conf_file)[ST_MODE])
181 self.log.debug('file permission is: %o', mode)
182 if mode & S_IRWXO or mode & S_IRWXG:
183 self.log.warning('File is readable by "other" and/or' +
184 ' "group" (actual permission %o octal).' %
186 self.log.warning('Consider setting permissions' +
189 def supersedes_config_with_cmd_line_options(self):
190 """Updates defaults settings with command line options"""
191 for sec in self.config.sections():
192 for opt in self.config.options(sec):
193 if opt in list(self.startopt.keys()):
194 self.config.set(sec, opt, str(self.startopt.get(opt)))
195 # honor MPD_HOST format as in mpc(1) for command line option --host
196 if self.startopt.get('host'):
197 if '@' in self.startopt.get('host'):
198 passwd, host = self.startopt.get('host').split('@')
199 self.config.set('MPD', 'password', passwd)
200 self.config.set('MPD', 'host', host)
203 """Use MPD en.var. to set defaults"""
204 mpd_host, mpd_port, passwd = utils.get_mpd_environ()
206 self.log.info('Env. variable MPD_HOST set to "%s"', mpd_host)
207 self.config['MPD'].update(host=mpd_host)
209 self.log.info('Env. variable MPD_HOST contains password.')
210 self.config['MPD'].update(password=passwd)
212 self.log.info('Env. variable MPD_PORT set to "%s".', mpd_port)
213 self.config['MPD'].update(port=mpd_port)
215 def init_config(self):
217 Use XDG directory standard if exists
218 else use "HOME/(.config|.local/share)/sima/"
219 http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
222 homedir = environ.get('HOME')
224 if environ.get('XDG_DATA_HOME'):
225 data_dir = join(environ.get('XDG_DATA_HOME'), DIRNAME)
226 elif homedir and isdir(homedir) and homedir not in ['/']:
227 data_dir = join(homedir, '.local', 'share', DIRNAME)
229 self.log.critical('Can\'t find a suitable location for data folder (XDG_DATA_HOME)')
230 self.log.critical('Please use "--var-dir" to set a proper location')
233 if self.startopt.get('conf_file'):
234 # No need to handle conf file location
236 elif environ.get('XDG_CONFIG_HOME'):
237 conf_dir = join(environ.get('XDG_CONFIG_HOME'), DIRNAME)
238 elif homedir and isdir(homedir) and homedir not in ['/']:
239 conf_dir = join(homedir, '.config', DIRNAME)
240 self.conf_file = join(conf_dir, CONF_FILE)
242 self.log.critical('Can\'t find a suitable location for config folder (XDG_CONFIG_HOME)')
243 self.log.critical('Please use "--config" to locate the conf file')
247 self.config['sima']['var_dir'] = join(data_dir)
249 # If no conf file present, uses defaults
250 if not isfile(self.conf_file):
253 self.log.info('Loading configuration from: %s', self.conf_file)
257 self.config.read(self.conf_file)
263 # vim: ai ts=4 sw=4 sts=4 expandtab