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
102 class ConfMan: # CONFIG MANAGER CLASS
104 Configuration manager.
105 Default configuration is stored in DEFAULT_CONF dictionnary.
106 First init_config() run to get config from file.
107 Then control_conf() is run and retrieve configuration from defaults if not
109 These settings are then updated with command line options with
110 supersedes_config_with_cmd_line_options().
112 Order of priority for the origin of an option is then (lowest to highest):
114 * Env. Var for MPD host, port and password
115 * configuration file (overrides previous)
116 * command line options (overrides previous)
119 def __init__(self, options=None):
120 self.log = logging.getLogger('sima')
121 # options settings priority:
122 # defauts < env. var. < conf. file < command line
123 self.conf_file = options.get('conf_file')
124 self.config = configparser.ConfigParser(inline_comment_prefixes='#')
125 self.config.read_dict(DEFAULT_CONF)
126 # update DEFAULT_CONF with env. var.
128 self.startopt = options
132 self.supersedes_config_with_cmd_line_options()
133 # Controls files access
136 self.config['sima']['db_file'] = join(self.config['sima']['var_dir'], 'sima.db')
139 data_dir = self.config['sima']['var_dir']
140 if not isdir(data_dir):
141 self.log.trace('Creating "%s"', data_dir)
143 chmod(data_dir, 0o700)
145 def control_facc(self):
146 """Controls file access.
147 This is relevant only for file provided through the configuration file
148 since files provided on the command line are already checked with
152 for op, ftochk in [('logfile', self.config.get('log', 'logfile')),
153 ('pidfile', self.config.get('daemon', 'pidfile')),]:
157 self.log.critical('Need a file not a directory: "%s"', ftochk)
159 if not exists(ftochk):
160 # Is parent directory writable then
161 filedir = dirname(ftochk)
162 if not access(filedir, W_OK):
163 self.log.critical('no write access to "%s" (%s)', filedir, op)
166 if not access(ftochk, W_OK):
167 self.log.critical('no write access to "%s" (%s)', ftochk, op)
170 if exists(self.conf_file):
171 self.log.warning('Try to check the configuration file: %s', self.conf_file)
174 def control_mod(self):
176 Controls conf file permissions.
178 mode = S_IMODE(stat(self.conf_file)[ST_MODE])
179 self.log.debug('file permission is: %o', mode)
180 if mode & S_IRWXO or mode & S_IRWXG:
181 self.log.warning('File is readable by "other" and/or' +
182 ' "group" (actual permission %o octal).' %
184 self.log.warning('Consider setting permissions' +
187 def supersedes_config_with_cmd_line_options(self):
188 """Updates defaults settings with command line options"""
189 for sec in self.config.sections():
190 for opt in self.config.options(sec):
191 if opt in list(self.startopt.keys()):
192 self.config.set(sec, opt, str(self.startopt.get(opt)))
193 # honor MPD_HOST format as in mpc(1) for command line option --host
194 if self.startopt.get('host'):
195 if '@' in self.startopt.get('host'):
196 passwd, host = self.startopt.get('host').split('@')
197 self.config.set('MPD', 'password', passwd)
198 self.config.set('MPD', 'host', host)
201 """Use MPD en.var. to set defaults"""
202 mpd_host, mpd_port, passwd = utils.get_mpd_environ()
204 self.log.info('Env. variable MPD_HOST set to "%s"', mpd_host)
205 self.config['MPD'].update(host=mpd_host)
207 self.log.info('Env. variable MPD_HOST contains password.')
208 self.config['MPD'].update(password=passwd)
210 self.log.info('Env. variable MPD_PORT set to "%s".', mpd_port)
211 self.config['MPD'].update(port=mpd_port)
213 def init_config(self):
215 Use XDG directory standard if exists
216 else use "HOME/(.config|.local/share)/sima/"
217 http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
220 homedir = environ.get('HOME')
222 if environ.get('XDG_DATA_HOME'):
223 data_dir = join(environ.get('XDG_DATA_HOME'), DIRNAME)
224 elif homedir and isdir(homedir) and homedir not in ['/']:
225 data_dir = join(homedir, '.local', 'share', DIRNAME)
227 self.log.critical('Can\'t find a suitable location for data folder (XDG_DATA_HOME)')
228 self.log.critical('Please use "--var-dir" to set a proper location')
231 if self.startopt.get('conf_file'):
232 # No need to handle conf file location
234 elif environ.get('XDG_CONFIG_HOME'):
235 conf_dir = join(environ.get('XDG_CONFIG_HOME'), DIRNAME)
236 elif homedir and isdir(homedir) and homedir not in ['/']:
237 conf_dir = join(homedir, '.config', DIRNAME)
238 self.conf_file = join(conf_dir, CONF_FILE)
240 self.log.critical('Can\'t find a suitable location for config folder (XDG_CONFIG_HOME)')
241 self.log.critical('Please use "--config" to locate the conf file')
245 self.config['sima']['var_dir'] = join(data_dir)
247 # If no conf file present, uses defaults
248 if not isfile(self.conf_file):
251 self.log.info('Loading configuration from: %s', self.conf_file)
255 self.config.read(self.conf_file)
261 # vim: ai ts=4 sw=4 sts=4 expandtab