1 # -*- coding: utf-8 -*-
3 # Copyright (c) 2010, 2011, 2013 Jack 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
28 from argparse import ArgumentError, Action
29 from base64 import b64decode as push
30 from codecs import getencoder
31 from datetime import datetime, timedelta
32 from os import environ, access, getcwd, W_OK, R_OK
33 from os.path import dirname, isabs, join, normpath, exists, isdir, isfile
34 from time import sleep
39 Decode Obfuscated api key.
40 Only preventing API keys harvesting over the network
41 https://developer.echonest.com/forums/thread/105
43 aka = push(bytes(dic.get('apikey') + '=', 'utf-8'))
44 aka = getencoder('rot-13')(str((aka), 'utf-8'))[0]
45 dic.update({'apikey':aka})
47 def get_mpd_environ():
49 Retrieve MPD env. var.
52 mpd_host_env = environ.get('MPD_HOST')
55 # mpd_host_env = ['pass', 'host'] because MPD_HOST=pass@host
56 mpd_host_env = mpd_host_env.split('@')
57 mpd_host_env.reverse()
58 host = mpd_host_env[0]
59 if len(mpd_host_env) > 1 and mpd_host_env[1]:
60 passwd = mpd_host_env[1]
61 return (host, environ.get('MPD_PORT', None), passwd)
63 def normalize_path(path):
67 return normpath(join(getcwd(), path))
71 """Log unknown exceptions"""
73 log = logging.getLogger('sima')
74 log.error('Unhandled Exception!!!')
75 log.error(''.join(traceback.format_exc()))
76 log.info('Please report the previous message'
77 ' along with some log entries right before the crash.')
78 log.info('thanks for your help :)')
79 log.info('Quiting now!')
82 def purge_cache(obj, age=4):
83 """purge old entries in http client cache
85 now = datetime.utcnow()
86 if now.hour == obj.timestamp.hour:
88 obj.timestamp = datetime.utcnow()
90 delta = timedelta(hours=age)
91 for url in list(cache.keys()):
92 timestamp = cache.get(url).created()
93 if now - timestamp > delta:
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')
109 class FileAction(Action):
110 """Generic class to inherit from for ArgParse action on file/dir
112 # pylint: disable=R0903
113 def __call__(self, parser, namespace, values, option_string=None):
114 self._file = normalize_path(values)
115 self._dir = dirname(self._file)
118 setattr(namespace, self.dest, self._file)
125 class Wfile(FileAction):
126 # pylint: disable=R0903
130 if not exists(self._dir):
131 #raise ArgumentError(self, '"{0}" does not exist'.format(self._dir))
132 self.parser.error('file does not exist: {0}'.format(self._dir))
133 if not exists(self._file):
134 # Is parent directory writable then
135 if not access(self._dir, W_OK):
136 self.parser.error('no write access to "{0}"'.format(self._dir))
138 if not access(self._file, W_OK):
139 self.parser.error('no write access to "{0}"'.format(self._file))
141 class Rfile(FileAction):
142 # pylint: disable=R0903
146 if not exists(self._file):
147 self.parser.error('file does not exist: {0}'.format(self._file))
148 if not isfile(self._file):
149 self.parser.error('not a file: {0}'.format(self._file))
150 if not access(self._file, R_OK):
151 self.parser.error('no read access to "{0}"'.format(self._file))
153 class Wdir(FileAction):
154 # pylint: disable=R0903
155 """Is directory writable
158 if not exists(self._file):
159 self.parser.error('directory does not exist: {0}'.format(self._file))
160 if not isdir(self._file):
161 self.parser.error('not a directory: {0}'.format(self._file))
162 if not access(self._file, W_OK):
163 self.parser.error('no write access to "{0}"'.format(self._file))
166 """throttle decorator"""
167 def __init__(self, wait):
169 self.last_called = datetime.now()
171 def __call__(self, func):
172 def wrapper(*args, **kwargs):
173 while self.last_called + self.wait > datetime.now():
175 result = func(*args, **kwargs)
176 self.last_called = datetime.now()
181 """Plain cache object"""
182 def __init__(self, elem, last=None):
184 self.requestdate = last
186 self.requestdate = datetime.utcnow()
189 return self.requestdate
195 # http client exceptions (for webservices)
197 class WSError(Exception):
200 class WSNotFound(WSError):
203 class WSTimeout(WSError):
206 class WSHTTPError(WSError):
211 # vim: ai ts=4 sw=4 sts=4 expandtab