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