]> kaliko git repositories - mpd-sima.git/blob - sima/utils/utils.py
Migrate simafm to persistent http caching
[mpd-sima.git] / sima / utils / utils.py
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (c) 2010, 2011, 2013, 2014 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
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
83 class SigHup(Exception):
84     """SIGHUP raises this Exception"""
85     pass
86
87 # ArgParse Callbacks
88 class Obsolete(Action):
89     # pylint: disable=R0903
90     """Deal with obsolete arguments
91     """
92     def __call__(self, parser, namespace, values, option_string=None):
93         raise ArgumentError(self, 'obsolete argument')
94
95 class FileAction(Action):
96     """Generic class to inherit from for ArgParse action on file/dir
97     """
98     # pylint: disable=R0903
99     def __call__(self, parser, namespace, values, option_string=None):
100         self._file = normalize_path(values)
101         self._dir = dirname(self._file)
102         self.parser = parser
103         self.checks()
104         setattr(namespace, self.dest, self._file)
105
106     def checks(self):
107         """control method
108         """
109         pass
110
111 class Wfile(FileAction):
112     # pylint: disable=R0903
113     """Is file writable
114     """
115     def checks(self):
116         if not exists(self._dir):
117             #raise ArgumentError(self, '"{0}" does not exist'.format(self._dir))
118             self.parser.error('file does not exist: {0}'.format(self._dir))
119         if not exists(self._file):
120             # Is parent directory writable then
121             if not access(self._dir, W_OK):
122                 self.parser.error('no write access to "{0}"'.format(self._dir))
123         else:
124             if not access(self._file, W_OK):
125                 self.parser.error('no write access to "{0}"'.format(self._file))
126
127 class Rfile(FileAction):
128     # pylint: disable=R0903
129     """Is file readable
130     """
131     def checks(self):
132         if not exists(self._file):
133             self.parser.error('file does not exist: {0}'.format(self._file))
134         if not isfile(self._file):
135             self.parser.error('not a file: {0}'.format(self._file))
136         if not access(self._file, R_OK):
137             self.parser.error('no read access to "{0}"'.format(self._file))
138
139 class Wdir(FileAction):
140     # pylint: disable=R0903
141     """Is directory writable
142     """
143     def checks(self):
144         if not exists(self._file):
145             self.parser.error('directory does not exist: {0}'.format(self._file))
146         if not isdir(self._file):
147             self.parser.error('not a directory: {0}'.format(self._file))
148         if not access(self._file, W_OK):
149             self.parser.error('no write access to "{0}"'.format(self._file))
150
151 class Throttle:
152     """throttle decorator"""
153     def __init__(self, wait):
154         self.wait = wait
155         self.last_called = datetime.now()
156
157     def __call__(self, func):
158         def wrapper(*args, **kwargs):
159             while self.last_called + self.wait > datetime.now():
160                 sleep(0.1)
161             result = func(*args, **kwargs)
162             self.last_called = datetime.now()
163             return result
164         return wrapper
165
166 # http client exceptions (for webservices)
167
168 class WSError(Exception):
169     pass
170
171 class WSNotFound(WSError):
172     pass
173
174 class WSTimeout(WSError):
175     pass
176
177 class WSHTTPError(WSError):
178     pass
179
180
181 # VIM MODLINE
182 # vim: ai ts=4 sw=4 sts=4 expandtab