]> kaliko git repositories - mpd-sima.git/blob - sima/utils/utils.py
Some clean-up (pylint audit)
[mpd-sima.git] / sima / utils / utils.py
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (c) 2010, 2011, 2013 Jack Kaliko <kaliko@azylum.org>
4 #
5 #  This file is part of sima
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 #
21 """generic tools and utilities for sima
22 """
23 # pylint: disable=C0111
24
25 import traceback
26 import sys
27
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
35
36
37 def getws(dic):
38     """
39     Decode Obfuscated api key.
40     Only preventing API keys harvesting over the network
41     https://developer.echonest.com/forums/thread/105
42     """
43     aka = push(bytes(dic.get('apikey') + '=', 'utf-8'))
44     aka = getencoder('rot-13')(str((aka), 'utf-8'))[0]
45     dic.update({'apikey':aka})
46
47 def get_mpd_environ():
48     """
49     Retrieve MPD env. var.
50     """
51     passwd = host = None
52     mpd_host_env = environ.get('MPD_HOST')
53     if mpd_host_env:
54         # If password is set:
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)
62
63 def normalize_path(path):
64     """Get absolute path
65     """
66     if not isabs(path):
67         return normpath(join(getcwd(), path))
68     return path
69
70 def exception_log():
71     """Log unknown exceptions"""
72     import logging
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!')
80     sys.exit(1)
81
82 def purge_cache(obj, age=4):
83     """purge old entries in http client cache
84     """
85     now = datetime.utcnow()
86     if now.hour == obj.timestamp.hour:
87         return
88     obj.timestamp = datetime.utcnow()
89     cache = obj.cache
90     delta = timedelta(hours=age)
91     for url in list(cache.keys()):
92         timestamp = cache.get(url).created()
93         if now - timestamp > delta:
94             cache.pop(url)
95
96
97 class SigHup(Exception):
98     """SIGHUP raises this Exception"""
99     pass
100
101 # ArgParse Callbacks
102 class Obsolete(Action):
103     # pylint: disable=R0903
104     """Deal with obsolete arguments
105     """
106     def __call__(self, parser, namespace, values, option_string=None):
107         raise ArgumentError(self, 'obsolete argument')
108
109 class FileAction(Action):
110     """Generic class to inherit from for ArgParse action on file/dir
111     """
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)
116         self.parser = parser
117         self.checks()
118         setattr(namespace, self.dest, self._file)
119
120     def checks(self):
121         """control method
122         """
123         pass
124
125 class Wfile(FileAction):
126     # pylint: disable=R0903
127     """Is file writable
128     """
129     def checks(self):
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))
137         else:
138             if not access(self._file, W_OK):
139                 self.parser.error('no write access to "{0}"'.format(self._file))
140
141 class Rfile(FileAction):
142     # pylint: disable=R0903
143     """Is file readable
144     """
145     def checks(self):
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))
152
153 class Wdir(FileAction):
154     # pylint: disable=R0903
155     """Is directory writable
156     """
157     def checks(self):
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))
164
165 class Throttle:
166     """throttle decorator"""
167     def __init__(self, wait):
168         self.wait = wait
169         self.last_called = datetime.now()
170
171     def __call__(self, func):
172         def wrapper(*args, **kwargs):
173             while self.last_called + self.wait > datetime.now():
174                 sleep(0.1)
175             result = func(*args, **kwargs)
176             self.last_called = datetime.now()
177             return result
178         return wrapper
179
180 class Cache:
181     """Plain cache object"""
182     def __init__(self, elem, last=None):
183         self.elem = elem
184         self.requestdate = last
185         if not last:
186             self.requestdate = datetime.utcnow()
187
188     def created(self):
189         return self.requestdate
190
191     def get(self):
192         return self.elem
193
194
195 # http client exceptions (for webservices)
196
197 class WSError(Exception):
198     pass
199
200 class WSNotFound(WSError):
201     pass
202
203 class WSTimeout(WSError):
204     pass
205
206 class WSHTTPError(WSError):
207     pass
208
209
210 # VIM MODLINE
211 # vim: ai ts=4 sw=4 sts=4 expandtab