15
15
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
# The developers of the Exaile media player hereby grant permission
19
# for non-GPL compatible GStreamer and Exaile plugins to be used and
20
# distributed together with GStreamer and Exaile. This permission is
21
# above and beyond the permissions granted by the GPL license by which
22
# Exaile is covered. If you modify this code, you may extend this
23
# exception to your version of the code, but you are not obligated to
24
# do so. If you do not wish to do so, delete this exception statement
18
# The developers of the Exaile media player hereby grant permission
19
# for non-GPL compatible GStreamer and Exaile plugins to be used and
20
# distributed together with GStreamer and Exaile. This permission is
21
# above and beyond the permissions granted by the GPL license by which
22
# Exaile is covered. If you modify this code, you may extend this
23
# exception to your version of the code, but you are not obligated to
24
# do so. If you do not wish to do so, delete this exception statement
25
25
# from your version.
27
import unicodedata, locale, logging, os, random, re, string, threading, time, traceback, \
29
35
from functools import wraps
30
from xl.nls import gettext as _
36
from collections import deque
37
from UserDict import DictMixin
32
39
logger = logging.getLogger(__name__)
33
40
_TESTING = False # set to True for testing
42
#TODO: get rid of this. only plugins/cd/ uses it.
36
44
# Ogg Vorbis spec tags
37
45
"title version album tracknumber artist genre performer copyright "
89
96
thread is allowed to access it at a time
92
def wrapper(self,*__args,**__kw):
99
def wrapper(self, *__args, **__kw):
94
101
rlock = self._sync_lock
95
102
except AttributeError:
96
103
from threading import RLock
97
rlock = self.__dict__.setdefault('_sync_lock',RLock())
104
rlock = self.__dict__.setdefault('_sync_lock', RLock())
100
return func(self,*__args,**__kw)
107
return func(self, *__args, **__kw)
106
# this code stolen from listen-gnome
107
""" Parse a date and return a time object """
108
""" Date like Thu, 02 02 2005 10:25:21 ... """
109
def strdate_to_time(date):
112
if (c == '+') or (c == '-'):
115
#FIXME : don't remove use it in strptime
117
if c in ["GMT","CST","EST","PST","EDT","PDT"]:
120
#Remove day because some field have incorrect string
126
#trying multiple date formats
129
#Set locale to C to parse date
130
locale.setlocale(locale.LC_TIME, "C")
132
formats = ["%d %b %Y %H:%M:%S",#without day, short month
133
"%d %B %Y %H:%M:%S",#without day, full month
134
"%d %b %Y",#only date , short month
135
"%d %B %Y",#only date , full month
136
"%b %d %Y %H:%M:%S",#without day, short month
137
"%B %d %Y %H:%M:%S",#without day, full month
138
"%b %d %Y",#only date , short month
139
"%B %d %Y",#only date , full month
142
for format in formats:
144
new_date = time.strptime(date,format)
148
locale.setlocale(locale.LC_TIME, '')
152
return time.mktime(new_date)
114
Decorator to profile a function
116
import hotshot, hotshot.stats
118
def wrapper(*args, **kwargs):
119
prof = hotshot.Profile("profiling.data")
120
res = prof.runcall(func, *args, **kwargs)
122
stats = hotshot.stats.load("profiling.data")
124
stats.sort_stats('time', 'calls')
125
print ">>>---- Begin profiling print"
127
print ">>>---- End profiling print"
154
131
def escape_xml(text):
161
138
text = text.replace(old, new)
164
def local_file_from_url(url):
166
Returns a local file path based on a url. If you get strange errors,
167
try running .encode() on the result
169
split = urlparse.urlsplit(url)
170
return urlparse.urlunsplit(('', '') + split[2:])
174
Case insensitive dictionary
178
Initializes the dictionary
180
self.keys_dict = dict()
183
def __setitem__(self, item, val):
185
Sets an item in the dict
187
if item is None: return
188
dict.__setitem__(self, item.lower(), val)
189
if hasattr(self, 'keys_dict'):
190
self.keys_dict[item.lower()] = item
192
def __getitem__(self, item):
194
Gets an item from the dict
196
return dict.__getitem__(self, item.lower())
198
def __contains__(self, key):
200
Returns True if this dictionary contains the specified key
202
return self.has_key(key)
204
def __delitem__(self, key=None):
205
if key is None: return
207
dict.__delitem__(self, key)
208
del self.keys_dict[key]
210
def has_key(self, key):
212
Returns True if this dictionary contains the specified key
216
return dict.has_key(self, key.lower())
220
Returns the case sensitive values of the keys
222
return self.keys_dict.values()
224
from UserDict import DictMixin
225
class odict(DictMixin):
227
An dictionary which keeps track
228
of the order of added items
230
Cherrypicked from http://code.activestate.com/recipes/496761/
232
def __init__(self, data=None, **kwdata):
237
if hasattr(data, 'items'):
241
for i in xrange(len(items)):
242
length = len(items[i])
244
raise ValueError('dictionary update sequence element '
245
'#%d has length %d; 2 is required' % (i, length))
246
self._keys.append(items[i][0])
247
self._data[items[i][0]] = items[i][1]
249
self._merge_keys(kwdata.iterkeys())
252
def __setitem__(self, key, value):
253
if key not in self._data:
254
self._keys.append(key)
255
self._data[key] = value
257
def __getitem__(self, key):
258
return self._data[key]
260
def __delitem__(self, key):
262
self._keys.remove(key)
266
for key in self._keys:
267
result.append('(%s, %s)' % (repr(key), repr(self._data[key])))
268
return ''.join(['OrderedDict', '([', ', '.join(result), '])'])
271
for key in self._keys:
274
def _merge_keys(self, keys):
275
self._keys.extend(keys)
277
self._keys = [newkeys.setdefault(x, x) for x in self._keys
280
def update(self, data):
282
if hasattr(data, 'iterkeys'):
283
self._merge_keys(data.iterkeys())
285
self._merge_keys(data.keys())
286
self._data.update(data)
289
return list(self._keys)
293
copyDict._data = self._data.copy()
294
copyDict._keys = self._keys[:]
297
141
def random_string(n):
299
143
returns a random string of length n, comprised of ascii characters
303
147
s += random.choice(string.ascii_letters)
306
def the_cutter(field):
308
Cuts "the"-like words off of the beginning of any field for better
311
lowered = field.lower()
312
for word in ("el ", "l'", "la ", "le ", "les ", "los ", "the "):
313
if lowered.startswith(word):
314
field = field[len(word):]
318
def lstrip_special(field, cutter=False):
320
Strip special chars off the beginning of a field for sorting. If
321
stripping the chars leaves nothing the original field is returned with
322
only whitespace removed.
326
lowered = field.lower()
327
stripped = lowered.lstrip(" `~!@#$%^&*()_+-={}|[]\\\";'<>?,./")
331
ret = lowered.lstrip()
333
ret = the_cutter(ret)
337
def normalize(field):
339
Normalizes a utf8 string into a fully developed form
341
return unicodedata.normalize('NFD', field)
343
def strip_marks(field):
345
Removes non spacing marks from a string (that is accents, mainly)
347
return ''.join((c for c in normalize(field) if unicodedata.category(c) != 'Mn'))
349
150
class VersionError(Exception):
351
152
Represents version discrepancies
353
154
def __init__(self, message):
155
Exception.__init__(self)
354
156
self.message = message
356
158
def __str__(self):
357
159
return repr(self.message)
359
# python<2.5 compatibility. Drop this when python2.4 isn't used so much anymore.
163
Opens a file or folder using the system configured program
165
platform = sys.platform
166
if platform == 'win32':
167
# pylint will error here on non-windows platforms unless we do this
168
# pylint: disable-msg=E1101
170
# pylint: enable-msg=E1101
171
elif platform == 'darwin':
172
subprocess.Popen(["open", path])
174
subprocess.Popen(["xdg-open", path])
176
def open_file_directory(path):
178
Opens the parent directory of a file, selecting the file if possible.
182
platform = sys.platform
183
if platform == 'win32':
184
subprocess.Popen(["explorer", "/select,", f.get_parse_name()])
185
elif platform == 'darwin':
186
subprocess.Popen(["open", f.get_parent().get_parse_name()])
188
subprocess.Popen(["xdg-open", f.get_parent().get_parse_name()])
190
class LimitedCache(DictMixin):
192
Simple cache that acts much like a dict, but has a maximum # of items
194
def __init__(self, limit):
200
return self.cache.__iter__()
202
def __contains__(self, item):
203
return self.cache.__contains__(item)
205
def __delitem__(self, item):
207
self.order.remove(item)
209
def __getitem__(self, item):
210
val = self.cache[item]
211
self.order.remove(item)
212
self.order.append(item)
215
def __setitem__(self, item, value):
216
self.cache[item] = value
217
self.order.append(item)
218
while len(self) > self.limit:
219
del self.cache[self.order.popleft()]
222
return self.cache.keys()
224
class cached(object):
226
Decorator to make a function's results cached
228
does not cache if there is an exception.
230
this probably breaks on functions that modify their arguments
232
def __init__(self, limit):
237
return frozenset(d.iteritems())
239
def __call__(self, f):
242
except AttributeError:
243
f._cache = LimitedCache(self.limit)
245
def wrapper(*args, **kwargs):
247
return f._cache[(args, self._freeze(kwargs))]
250
ret = f(*args, **kwargs)
251
f._cache[(args, self._freeze(kwargs))] = ret
368
256
# vim: et sts=4 sw=4