]> kaliko git repositories - mpd-sima.git/blob - sima/utils/utils.py
Refactored lastfm/echonest webservices
[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
24 import traceback
25 import sys
26
27 from argparse import ArgumentError, Action
28 from base64 import b64decode as push
29 from codecs import getencoder
30 from datetime import datetime, timedelta
31 from os import environ, access, getcwd, W_OK, R_OK
32 from os.path import dirname, isabs, join, normpath, exists, isdir, isfile
33 from time import sleep
34
35
36 def getws(dic):
37     """
38     Decode Obfuscated api key.
39     Only preventing API keys harvesting over the network
40     https://developer.echonest.com/forums/thread/105
41     """
42     aka = push(bytes(dic.get('apikey') + '=', 'utf-8'))
43     aka = getencoder('rot-13')(str((aka), 'utf-8'))[0]
44     dic.update({'apikey':aka})
45
46 def get_mpd_environ():
47     """
48     Retrieve MPD env. var.
49     """
50     passwd = host = None
51     mpd_host_env = environ.get('MPD_HOST')
52     if mpd_host_env:
53         # If password is set:
54         # mpd_host_env = ['pass', 'host'] because MPD_HOST=pass@host
55         mpd_host_env = mpd_host_env.split('@')
56         mpd_host_env.reverse()
57         host = mpd_host_env[0]
58         if len(mpd_host_env) > 1 and mpd_host_env[1]:
59             passwd = mpd_host_env[1]
60     return (host, environ.get('MPD_PORT', None), passwd)
61
62 def normalize_path(path):
63     """Get absolute path
64     """
65     if not isabs(path):
66         return normpath(join(getcwd(), path))
67     return path
68
69 def exception_log():
70     """Log unknown exceptions"""
71     import logging
72     log = logging.getLogger('sima')
73     log.error('Unhandled Exception!!!')
74     log.error(''.join(traceback.format_exc()))
75     log.info('Please report the previous message'
76              ' along with some log entries right before the crash.')
77     log.info('thanks for your help :)')
78     log.info('Quiting now!')
79     sys.exit(1)
80
81 def purge_cache(obj, age=4):
82     now = datetime.utcnow()
83     if now.hour == obj.timestamp.hour:
84         return
85     obj.timestamp = datetime.utcnow()
86     cache = obj.cache
87     delta = timedelta(hours=age)
88     for url in list(cache.keys()):
89         timestamp = cache.get(url).created()
90         if now - timestamp > delta:
91             cache.pop(url)
92
93
94 class SigHup(Exception):
95     pass
96
97 # ArgParse Callbacks
98 class Obsolete(Action):
99     # pylint: disable=R0903
100     """Deal with obsolete arguments
101     """
102     def __call__(self, parser, namespace, values, option_string=None):
103         raise ArgumentError(self, 'obsolete argument')
104
105 class FileAction(Action):
106     """Generic class to inherit from for ArgParse action on file/dir
107     """
108     # pylint: disable=R0903
109     def __call__(self, parser, namespace, values, option_string=None):
110         self._file = normalize_path(values)
111         self._dir = dirname(self._file)
112         self.parser = parser
113         self.checks()
114         setattr(namespace, self.dest, self._file)
115
116     def checks(self):
117         """control method
118         """
119         pass
120
121 class Wfile(FileAction):
122     # pylint: disable=R0903
123     """Is file writable
124     """
125     def checks(self):
126         if not exists(self._dir):
127             #raise ArgumentError(self, '"{0}" does not exist'.format(self._dir))
128             self.parser.error('file does not exist: {0}'.format(self._dir))
129         if not exists(self._file):
130             # Is parent directory writable then
131             if not access(self._dir, W_OK):
132                 self.parser.error('no write access to "{0}"'.format(self._dir))
133         else:
134             if not access(self._file, W_OK):
135                 self.parser.error('no write access to "{0}"'.format(self._file))
136
137 class Rfile(FileAction):
138     # pylint: disable=R0903
139     """Is file readable
140     """
141     def checks(self):
142         if not exists(self._file):
143             self.parser.error('file does not exist: {0}'.format(self._file))
144         if not isfile(self._file):
145             self.parser.error('not a file: {0}'.format(self._file))
146         if not access(self._file, R_OK):
147             self.parser.error('no read access to "{0}"'.format(self._file))
148
149 class Wdir(FileAction):
150     # pylint: disable=R0903
151     """Is directory writable
152     """
153     def checks(self):
154         if not exists(self._file):
155             self.parser.error('directory does not exist: {0}'.format(self._file))
156         if not isdir(self._file):
157             self.parser.error('not a directory: {0}'.format(self._file))
158         if not access(self._file, W_OK):
159             self.parser.error('no write access to "{0}"'.format(self._file))
160
161 class Throttle():
162     def __init__(self, wait):
163         self.wait = wait
164         self.last_called = datetime.now()
165
166     def __call__(self, func):
167         def wrapper(*args, **kwargs):
168             while self.last_called + self.wait > datetime.now():
169                 sleep(0.1)
170             result = func(*args, **kwargs)
171             self.last_called = datetime.now()
172             return result
173         return wrapper
174
175 class Cache():
176     def __init__(self, elem, last=None):
177         self.elem = elem
178         self.requestdate = last
179         if not last:
180             self.requestdate = datetime.utcnow()
181
182     def created(self):
183         return self.requestdate
184
185     def get(self):
186         return self.elem
187
188
189 # http client exceptions (for webservices)
190
191 class WSError(Exception):
192     pass
193
194 class WSNotFound(WSError):
195     pass
196
197 class WSTimeout(WSError):
198     pass
199
200 class WSHTTPError(WSError):
201     pass
202
203
204 # VIM MODLINE
205 # vim: ai ts=4 sw=4 sts=4 expandtab