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
40 Decode Obfuscated api key.
41 Only preventing API keys harvesting over the network
42 https://developer.echonest.com/forums/thread/105
44 aka = push(bytes(dic.get('apikey') + '=', 'utf-8'))
45 aka = getencoder('rot-13')(str((aka), 'utf-8'))[0]
46 dic.update({'apikey': aka})
48 def parse_mpd_host(value):
50 # If password is set: MPD_HOST=pass@host
52 mpd_host_env = value.split('@', 1)
54 # A password is actually set
55 passwd = mpd_host_env[0]
57 host = mpd_host_env[1]
59 # No password set but leading @ is an abstract socket
60 host = '@'+mpd_host_env[1]
62 # MPD_HOST is a plain host
67 def get_mpd_environ():
69 Retrieve MPD env. var.
72 if getenv('MPD_HOST'):
73 host, passwd = parse_mpd_host(getenv('MPD_HOST'))
74 return (host, getenv('MPD_PORT', None), passwd)
77 def normalize_path(path):
81 return normpath(join(getcwd(), path))
86 """Log unknown exceptions"""
87 log = logging.getLogger(__name__)
88 log.error('Unhandled Exception!!!')
89 log.error(''.join(traceback.format_exc()))
90 log.info('Please report the previous message'
91 ' along with some log entries right before the crash.')
92 log.info('thanks for your help :)')
93 log.info('Quiting now!')
97 class SigHup(Exception):
98 """SIGHUP raises this Exception"""
102 class Obsolete(Action):
103 # pylint: disable=R0903
104 """Deal with obsolete arguments
106 def __call__(self, parser, namespace, values, option_string=None):
107 raise ArgumentError(self, 'obsolete argument')
110 class FileAction(Action):
111 """Generic class to inherit from for ArgParse action on file/dir
113 # pylint: disable=R0903
114 def __call__(self, parser, namespace, values, option_string=None):
115 self._file = normalize_path(values)
116 self._dir = dirname(self._file)
119 setattr(namespace, self.dest, self._file)
126 class Wfile(FileAction):
127 # pylint: disable=R0903
131 if isdir(self._file):
132 self.parser.error('need a file not a directory: {}'.format(self._file))
133 if not exists(self._dir):
134 #raise ArgumentError(self, '"{0}" does not exist'.format(self._dir))
135 self.parser.error('directory does not exist: {0}'.format(self._dir))
136 if not exists(self._file):
137 # Is parent directory writable then
138 if not access(self._dir, W_OK):
139 self.parser.error('no write access to "{0}"'.format(self._dir))
141 if not access(self._file, W_OK):
142 self.parser.error('no write access to "{0}"'.format(self._file))
145 class Rfile(FileAction):
146 # pylint: disable=R0903
150 if not exists(self._file):
151 self.parser.error('file does not exist: {0}'.format(self._file))
152 if not isfile(self._file):
153 self.parser.error('not a file: {0}'.format(self._file))
154 if not access(self._file, R_OK):
155 self.parser.error('no read access to "{0}"'.format(self._file))
158 class Wdir(FileAction):
159 # pylint: disable=R0903
160 """Is directory writable
163 if not exists(self._file):
164 self.parser.error('directory does not exist: {0}'.format(self._file))
165 if not isdir(self._file):
166 self.parser.error('not a directory: {0}'.format(self._file))
167 if not access(self._file, W_OK):
168 self.parser.error('no write access to "{0}"'.format(self._file))
172 """throttle decorator"""
173 def __init__(self, wait):
175 self.last_called = datetime.now()
177 def __call__(self, func):
178 def wrapper(*args, **kwargs):
179 while self.last_called + self.wait > datetime.now():
181 result = func(*args, **kwargs)
182 self.last_called = datetime.now()
187 class MPDSimaException(Exception):
188 """Generic MPD_sima Exception"""
191 # http client exceptions (for webservices)
192 class WSError(MPDSimaException):
196 class WSNotFound(WSError):
200 class WSTimeout(WSError):
204 class WSHTTPError(WSError):
208 class PluginException(MPDSimaException):
212 # vim: ai ts=4 sw=4 sts=4 expandtab