]> kaliko git repositories - mpd-sima.git/blob - sima/utils/utils.py
2cb7c357a8dbd080405031e17281298dc8a07ea5
[mpd-sima.git] / sima / utils / utils.py
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (c) 2010, 2011, 2013, 2014, 2015 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 isdir(self._file):
117             self.parser.error('need a file not a directory: {}'.format(self._file))
118         if not exists(self._dir):
119             #raise ArgumentError(self, '"{0}" does not exist'.format(self._dir))
120             self.parser.error('directory does not exist: {0}'.format(self._dir))
121         if not exists(self._file):
122             # Is parent directory writable then
123             if not access(self._dir, W_OK):
124                 self.parser.error('no write access to "{0}"'.format(self._dir))
125         else:
126             if not access(self._file, W_OK):
127                 self.parser.error('no write access to "{0}"'.format(self._file))
128
129 class Rfile(FileAction):
130     # pylint: disable=R0903
131     """Is file readable
132     """
133     def checks(self):
134         if not exists(self._file):
135             self.parser.error('file does not exist: {0}'.format(self._file))
136         if not isfile(self._file):
137             self.parser.error('not a file: {0}'.format(self._file))
138         if not access(self._file, R_OK):
139             self.parser.error('no read access to "{0}"'.format(self._file))
140
141 class Wdir(FileAction):
142     # pylint: disable=R0903
143     """Is directory writable
144     """
145     def checks(self):
146         if not exists(self._file):
147             self.parser.error('directory does not exist: {0}'.format(self._file))
148         if not isdir(self._file):
149             self.parser.error('not a directory: {0}'.format(self._file))
150         if not access(self._file, W_OK):
151             self.parser.error('no write access to "{0}"'.format(self._file))
152
153 class Throttle:
154     """throttle decorator"""
155     def __init__(self, wait):
156         self.wait = wait
157         self.last_called = datetime.now()
158
159     def __call__(self, func):
160         def wrapper(*args, **kwargs):
161             while self.last_called + self.wait > datetime.now():
162                 sleep(0.1)
163             result = func(*args, **kwargs)
164             self.last_called = datetime.now()
165             return result
166         return wrapper
167
168 # http client exceptions (for webservices)
169
170 class WSError(Exception):
171     pass
172
173 class WSNotFound(WSError):
174     pass
175
176 class WSTimeout(WSError):
177     pass
178
179 class WSHTTPError(WSError):
180     pass
181
182
183 # VIM MODLINE
184 # vim: ai ts=4 sw=4 sts=4 expandtab