1 # -*- coding: utf-8 -*-
3 # Copyright (c) 2010, 2011, 2013, 2014, 2015, 2020 kaliko <kaliko@azylum.org>
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/>.
21 """generic tools and utilities for sima
23 # pylint: disable=C0111
29 from argparse import ArgumentError, Action
30 from base64 import b64decode as push
31 from codecs import getencoder
32 from datetime import datetime
33 from os import getenv, access, getcwd, W_OK, R_OK
34 from os.path import dirname, isabs, join, normpath, exists, isdir, isfile
35 from time import sleep
37 from musicpd import VERSION as mversion
38 from sima.info import __version__ as sversion
43 Decode Obfuscated api key.
44 Only preventing API keys harvesting over the network
45 https://developer.echonest.com/forums/thread/105
47 aka = push(bytes(dic.get('apikey') + '=', 'utf-8'))
48 aka = getencoder('rot-13')(str((aka), 'utf-8'))[0]
49 dic.update({'apikey': aka})
52 def parse_mpd_host(value):
54 # If password is set: MPD_HOST=pass@host
56 mpd_host_env = value.split('@', 1)
58 # A password is actually set
59 passwd = mpd_host_env[0]
61 host = mpd_host_env[1]
63 # No password set but leading @ is an abstract socket
64 host = '@'+mpd_host_env[1]
66 # MPD_HOST is a plain host
71 def get_mpd_environ():
73 Retrieve MPD env. var.
76 if getenv('MPD_HOST'):
77 host, passwd = parse_mpd_host(getenv('MPD_HOST'))
78 return (host, getenv('MPD_PORT', None), passwd)
81 def normalize_path(path):
85 return normpath(join(getcwd(), path))
90 """Log unknown exceptions"""
91 log = logging.getLogger(__name__)
92 log.error('Unhandled Exception!!!')
93 log.error(''.join(traceback.format_exc()))
94 log.info('musicpd python module version: %s', mversion)
95 log.info('MPD_sima version: %s', sversion)
96 log.info('Please report the previous message'
97 ' along with some log entries right before the crash.')
98 log.info('thanks for your help :)')
99 log.info('Quiting now!')
103 class SigHup(Exception):
104 """SIGHUP raises this Exception"""
108 class Obsolete(Action):
109 # pylint: disable=R0903
110 """Deal with obsolete arguments
112 def __call__(self, parser, namespace, values, option_string=None):
113 raise ArgumentError(self, 'obsolete argument')
116 class FileAction(Action):
117 """Generic class to inherit from for ArgParse action on file/dir
119 # pylint: disable=R0903
120 def __call__(self, parser, namespace, values, option_string=None):
121 self._file = normalize_path(values)
122 self._dir = dirname(self._file)
125 setattr(namespace, self.dest, self._file)
132 class Wfile(FileAction):
133 # pylint: disable=R0903
137 if isdir(self._file):
138 self.parser.error('need a file not a directory: {}'.format(self._file))
139 if not exists(self._dir):
140 #raise ArgumentError(self, '"{0}" does not exist'.format(self._dir))
141 self.parser.error('directory does not exist: {0}'.format(self._dir))
142 if not exists(self._file):
143 # Is parent directory writable then
144 if not access(self._dir, W_OK):
145 self.parser.error('no write access to "{0}"'.format(self._dir))
147 if not access(self._file, W_OK):
148 self.parser.error('no write access to "{0}"'.format(self._file))
151 class Rfile(FileAction):
152 # pylint: disable=R0903
156 if not exists(self._file):
157 self.parser.error('file does not exist: {0}'.format(self._file))
158 if not isfile(self._file):
159 self.parser.error('not a file: {0}'.format(self._file))
160 if not access(self._file, R_OK):
161 self.parser.error('no read access to "{0}"'.format(self._file))
164 class Wdir(FileAction):
165 # pylint: disable=R0903
166 """Is directory writable
169 if not exists(self._file):
170 self.parser.error('directory does not exist: {0}'.format(self._file))
171 if not isdir(self._file):
172 self.parser.error('not a directory: {0}'.format(self._file))
173 if not access(self._file, W_OK):
174 self.parser.error('no write access to "{0}"'.format(self._file))
178 """throttle decorator"""
179 def __init__(self, wait):
181 self.last_called = datetime.now()
183 def __call__(self, func):
184 def wrapper(*args, **kwargs):
185 while self.last_called + self.wait > datetime.now():
187 result = func(*args, **kwargs)
188 self.last_called = datetime.now()
193 class MPDSimaException(Exception):
194 """Generic MPD_sima Exception"""
197 # http client exceptions (for webservices)
198 class WSError(MPDSimaException):
202 class WSNotFound(WSError):
206 class WSTimeout(WSError):
210 class WSHTTPError(WSError):
214 class PluginException(MPDSimaException):
218 # vim: ai ts=4 sw=4 sts=4 expandtab