~ubuntu-branches/ubuntu/lucid/exaile/lucid

« back to all changes in this revision

Viewing changes to xl/common.py

  • Committer: Bazaar Package Importer
  • Author(s): Andrew Starr-Bochicchio
  • Date: 2010-02-12 19:51:01 UTC
  • mfrom: (1.1.11 upstream)
  • Revision ID: james.westby@ubuntu.com-20100212195101-8jt3tculxcl92e6v
Tags: 0.3.1~b1-0ubuntu1
* New upstream release.
* Adjust exaile.install for new plugins.
* debian/control:
 - Drop unneeded python-dev Build-Dep.
 - Bump Standards-Version to 3.8.4 
* debian/rules: No empty po files to delete.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2008-2009 Adam Olsen 
 
1
# Copyright (C) 2008-2009 Adam Olsen
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
15
15
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16
16
#
17
17
#
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.
26
26
 
27
 
import unicodedata, locale, logging, os, random, re, string, threading, time, traceback, \
28
 
    urllib, urlparse
 
27
import logging
 
28
import os
 
29
import random
 
30
import string
 
31
import subprocess
 
32
import sys
 
33
import threading
 
34
import traceback
29
35
from functools import wraps
30
 
from xl.nls import gettext as _
 
36
from collections import deque
 
37
from UserDict import DictMixin
31
38
 
32
39
logger = logging.getLogger(__name__)
33
40
_TESTING = False  # set to True for testing
34
41
 
 
42
#TODO: get rid of this. only plugins/cd/ uses it.
35
43
VALID_TAGS = (
36
44
    # Ogg Vorbis spec tags
37
45
    "title version album tracknumber artist genre performer copyright "
46
54
PICKLE_PROTOCOL=2
47
55
 
48
56
 
49
 
def get_default_encoding():
50
 
    return 'utf-8' # TODO: This is probably not right....
51
 
 
52
 
# log() exists only so as to not break compatibility, new code
53
 
# should not use it as it may br dropped in the future
54
 
def log(message):
55
 
    logger.info(message + "  (Warning, using deprecated logger)")
56
 
 
57
57
# use this for general logging of exceptions
58
58
def log_exception(log=logger, message="Exception caught!"):
 
59
    """
 
60
        Convenience function to log an exception + traceback
 
61
 
 
62
        :param log: the logger object to use.  important to specify
 
63
            so that it will be logged under the right module name.
 
64
        :param message: a message describing the error condition.
 
65
    """
59
66
    log.debug(message + "\n" + traceback.format_exc())
60
67
 
61
 
 
62
68
def to_unicode(x, default_encoding=None):
 
69
    """Force getting a unicode string from any object."""
63
70
    if isinstance(x, unicode):
64
71
        return x
65
72
    elif default_encoding and isinstance(x, str):
72
79
    """
73
80
        A decorator that will make any function run in a new thread
74
81
    """
75
 
    
 
82
 
76
83
    # TODO: make this bad hack unneeded
77
84
    if _TESTING: return f
78
85
    @wraps(f)
89
96
        thread is allowed to access it at a time
90
97
    """
91
98
    @wraps(func)
92
 
    def wrapper(self,*__args,**__kw):
 
99
    def wrapper(self, *__args, **__kw):
93
100
        try:
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())
98
105
        rlock.acquire()
99
106
        try:
100
 
            return func(self,*__args,**__kw)
 
107
            return func(self, *__args, **__kw)
101
108
        finally:
102
109
            rlock.release()
103
110
    return wrapper
104
111
 
105
 
 
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):
110
 
    #removing timezone
111
 
    c = date[-5:-4]
112
 
    if (c == '+') or (c == '-'):
113
 
        date = date[:-6]
114
 
 
115
 
    #FIXME : don't remove use it in strptime
116
 
    c = date[-3:]
117
 
    if c in ["GMT","CST","EST","PST","EDT","PDT"]:
118
 
        date = date[:-3]
119
 
 
120
 
    #Remove day because some field have incorrect string
121
 
    c = date.rfind(",")
122
 
    if c!=-1:
123
 
        date = date [c+1:]
124
 
    date = date.strip()
125
 
 
126
 
    #trying multiple date formats
127
 
    new_date = None
128
 
 
129
 
    #Set locale to C to parse date
130
 
    locale.setlocale(locale.LC_TIME, "C")
131
 
 
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
140
 
                "%Y-%m-%d %H:%M:%S",
141
 
                ]
142
 
    for format in formats:
143
 
        try:
144
 
            new_date = time.strptime(date,format)
145
 
        except ValueError:
146
 
            continue
147
 
 
148
 
    locale.setlocale(locale.LC_TIME, '')
149
 
    if new_date is None:
150
 
        return ""
151
 
 
152
 
    return time.mktime(new_date)
 
112
def profileit(func):
 
113
    """
 
114
        Decorator to profile a function
 
115
    """
 
116
    import hotshot, hotshot.stats
 
117
    @wraps(func)
 
118
    def wrapper(*args, **kwargs):
 
119
        prof = hotshot.Profile("profiling.data")
 
120
        res = prof.runcall(func, *args, **kwargs)
 
121
        prof.close()
 
122
        stats = hotshot.stats.load("profiling.data")
 
123
        stats.strip_dirs()
 
124
        stats.sort_stats('time', 'calls')
 
125
        print ">>>---- Begin profiling print"
 
126
        stats.print_stats()
 
127
        print ">>>---- End profiling print"
 
128
        return res
 
129
    return wrapper
153
130
 
154
131
def escape_xml(text):
155
132
    """
161
138
        text = text.replace(old, new)
162
139
    return text
163
140
 
164
 
def local_file_from_url(url):
165
 
    """
166
 
        Returns a local file path based on a url. If you get strange errors,
167
 
        try running .encode() on the result
168
 
    """
169
 
    split = urlparse.urlsplit(url)
170
 
    return urlparse.urlunsplit(('', '') + split[2:])
171
 
 
172
 
class idict(dict): 
173
 
    """
174
 
        Case insensitive dictionary
175
 
    """
176
 
    def __init__(self): 
177
 
        """
178
 
            Initializes the dictionary
179
 
        """
180
 
        self.keys_dict = dict()
181
 
        dict.__init__(self)
182
 
 
183
 
    def __setitem__(self, item, val): 
184
 
        """
185
 
            Sets an item in the dict
186
 
        """
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
191
 
    
192
 
    def __getitem__(self, item): 
193
 
        """
194
 
            Gets an item from the dict
195
 
        """
196
 
        return dict.__getitem__(self, item.lower())
197
 
 
198
 
    def __contains__(self, key): 
199
 
        """
200
 
            Returns True if this dictionary contains the specified key
201
 
        """
202
 
        return self.has_key(key)
203
 
 
204
 
    def __delitem__(self, key=None):
205
 
        if key is None: return
206
 
        key = key.lower()
207
 
        dict.__delitem__(self, key)
208
 
        del self.keys_dict[key]
209
 
 
210
 
    def has_key(self, key): 
211
 
        """
212
 
            Returns True if this dictionary contains the specified key
213
 
        """
214
 
        if key is None:
215
 
            return False
216
 
        return dict.has_key(self, key.lower())
217
 
 
218
 
    def keys(self): 
219
 
        """
220
 
            Returns the case sensitive values of the keys
221
 
        """
222
 
        return self.keys_dict.values()
223
 
 
224
 
from UserDict import DictMixin
225
 
class odict(DictMixin):
226
 
    """
227
 
        An dictionary which keeps track
228
 
        of the order of added items
229
 
 
230
 
        Cherrypicked from http://code.activestate.com/recipes/496761/
231
 
    """
232
 
    def __init__(self, data=None, **kwdata):
233
 
        self._keys = []
234
 
        self._data = {}
235
 
 
236
 
        if data is not None:
237
 
            if hasattr(data, 'items'):
238
 
                items = data.items()
239
 
            else:
240
 
                items = list(data)
241
 
            for i in xrange(len(items)):
242
 
                length = len(items[i])
243
 
                if length != 2:
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]
248
 
        if kwdata:
249
 
            self._merge_keys(kwdata.iterkeys())
250
 
            self.update(kwdata)
251
 
        
252
 
    def __setitem__(self, key, value):
253
 
        if key not in self._data:
254
 
            self._keys.append(key)
255
 
        self._data[key] = value
256
 
        
257
 
    def __getitem__(self, key):
258
 
        return self._data[key]
259
 
    
260
 
    def __delitem__(self, key):
261
 
        del self._data[key]
262
 
        self._keys.remove(key)
263
 
 
264
 
    def __repr__(self):
265
 
        result = []
266
 
        for key in self._keys:
267
 
            result.append('(%s, %s)' % (repr(key), repr(self._data[key])))
268
 
        return ''.join(['OrderedDict', '([', ', '.join(result), '])'])
269
 
 
270
 
    def __iter__(self):
271
 
        for key in self._keys:
272
 
            yield key
273
 
 
274
 
    def _merge_keys(self, keys):
275
 
        self._keys.extend(keys)
276
 
        newkeys = {}
277
 
        self._keys = [newkeys.setdefault(x, x) for x in self._keys
278
 
            if x not in newkeys]
279
 
 
280
 
    def update(self, data):
281
 
        if data is not None:
282
 
            if hasattr(data, 'iterkeys'):
283
 
                self._merge_keys(data.iterkeys())
284
 
            else:
285
 
                self._merge_keys(data.keys())
286
 
            self._data.update(data)
287
 
        
288
 
    def keys(self):
289
 
        return list(self._keys)
290
 
    
291
 
    def copy(self):
292
 
        copyDict = odict()
293
 
        copyDict._data = self._data.copy()
294
 
        copyDict._keys = self._keys[:]
295
 
        return copyDict
296
 
 
297
141
def random_string(n):
298
142
    """
299
143
        returns a random string of length n, comprised of ascii characters
300
144
    """
301
145
    s = ""
302
 
    for x in range(n):
 
146
    for i in xrange(n):
303
147
        s += random.choice(string.ascii_letters)
304
148
    return s
305
149
 
306
 
def the_cutter(field):
307
 
    """
308
 
        Cuts "the"-like words off of the beginning of any field for better
309
 
        sorting
310
 
    """
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):]
315
 
            break
316
 
    return field
317
 
 
318
 
def lstrip_special(field, cutter=False):
319
 
    """
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.
323
 
    """
324
 
    if field == None:
325
 
        return field
326
 
    lowered = field.lower()
327
 
    stripped = lowered.lstrip(" `~!@#$%^&*()_+-={}|[]\\\";'<>?,./")
328
 
    if stripped:
329
 
        ret = stripped
330
 
    else:
331
 
        ret = lowered.lstrip()
332
 
    if cutter:
333
 
        ret = the_cutter(ret)
334
 
    
335
 
    return ret
336
 
 
337
 
def normalize(field):
338
 
    """
339
 
        Normalizes a utf8 string into a fully developed form
340
 
    """
341
 
    return unicodedata.normalize('NFD', field)
342
 
 
343
 
def strip_marks(field):
344
 
    """
345
 
        Removes non spacing marks from a string (that is accents, mainly)
346
 
    """
347
 
    return ''.join((c for c in normalize(field) if unicodedata.category(c) != 'Mn'))
348
 
 
349
150
class VersionError(Exception):
350
151
    """
351
152
       Represents version discrepancies
352
153
    """
353
154
    def __init__(self, message):
 
155
        Exception.__init__(self)
354
156
        self.message = message
355
157
 
356
158
    def __str__(self):
357
159
        return repr(self.message)
358
160
 
359
 
# python<2.5 compatibility. Drop this when python2.4 isn't used so much anymore.
360
 
try:
361
 
    any = any
362
 
except NameError:
363
 
    def any(seq):
364
 
        for e in seq:
365
 
            if e: return True
366
 
        return False
 
161
def open_file(path):
 
162
    """
 
163
        Opens a file or folder using the system configured program
 
164
    """
 
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
 
169
        os.startfile(path)
 
170
        # pylint: enable-msg=E1101
 
171
    elif platform == 'darwin':
 
172
        subprocess.Popen(["open", path])
 
173
    else:
 
174
        subprocess.Popen(["xdg-open", path])
 
175
 
 
176
def open_file_directory(path):
 
177
    """
 
178
        Opens the parent directory of a file, selecting the file if possible.
 
179
    """
 
180
    import gio
 
181
    f = gio.File(path)
 
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()])
 
187
    else:
 
188
        subprocess.Popen(["xdg-open", f.get_parent().get_parse_name()])
 
189
 
 
190
class LimitedCache(DictMixin):
 
191
    """
 
192
        Simple cache that acts much like a dict, but has a maximum # of items
 
193
    """
 
194
    def __init__(self, limit):
 
195
        self.limit = limit
 
196
        self.order = deque()
 
197
        self.cache = dict()
 
198
 
 
199
    def __iter__(self):
 
200
        return self.cache.__iter__()
 
201
 
 
202
    def __contains__(self, item):
 
203
        return self.cache.__contains__(item)
 
204
 
 
205
    def __delitem__(self, item):
 
206
        del self.cache[item]
 
207
        self.order.remove(item)
 
208
 
 
209
    def __getitem__(self, item):
 
210
        val = self.cache[item]
 
211
        self.order.remove(item)
 
212
        self.order.append(item)
 
213
        return val
 
214
 
 
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()]
 
220
 
 
221
    def keys(self):
 
222
        return self.cache.keys()
 
223
 
 
224
class cached(object):
 
225
    """
 
226
        Decorator to make a function's results cached
 
227
 
 
228
        does not cache if there is an exception.
 
229
 
 
230
        this probably breaks on functions that modify their arguments
 
231
    """
 
232
    def __init__(self, limit):
 
233
        self.limit = limit
 
234
 
 
235
    @staticmethod
 
236
    def _freeze(d):
 
237
        return frozenset(d.iteritems())
 
238
 
 
239
    def __call__(self, f):
 
240
        try:
 
241
            f._cache
 
242
        except AttributeError:
 
243
            f._cache = LimitedCache(self.limit)
 
244
        @wraps(f)
 
245
        def wrapper(*args, **kwargs):
 
246
            try:
 
247
                return f._cache[(args, self._freeze(kwargs))]
 
248
            except KeyError:
 
249
                pass
 
250
            ret = f(*args, **kwargs)
 
251
            f._cache[(args, self._freeze(kwargs))] = ret
 
252
            return ret
 
253
        return wrapper
 
254
 
367
255
 
368
256
# vim: et sts=4 sw=4
369