~ubuntu-branches/debian/wheezy/quodlibet/wheezy

« back to all changes in this revision

Viewing changes to library/_library.py

  • Committer: Bazaar Package Importer
  • Author(s): Luca Falavigna
  • Date: 2009-01-30 23:55:34 UTC
  • mto: (18.1.1 squeeze) (2.1.9 sid)
  • mto: This revision was merged to the branch mainline in revision 23.
  • Revision ID: james.westby@ubuntu.com-20090130235534-45857nfsgobw4apc
Tags: upstream-2.0
ImportĀ upstreamĀ versionĀ 2.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright 2006 Joe Wreschnig
2
 
#
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License version 2 as
5
 
# published by the Free Software Foundation
6
 
#
7
 
# $Id: _library.py 4029 2007-04-27 06:01:12Z piman $
8
 
 
9
 
"""Base library classes.
10
 
 
11
 
These classes are the most basic library classes. As such they are the
12
 
least useful but most content-agnostic.
13
 
"""
14
 
 
15
 
import cPickle as pickle
16
 
import itertools
17
 
import os
18
 
import shutil
19
 
import traceback
20
 
 
21
 
import gobject
22
 
import gtk
23
 
 
24
 
# Windows doesn't have fcntl, just don't lock for now
25
 
try:
26
 
    import fcntl
27
 
except ImportError:
28
 
    fcntl = None 
29
 
 
30
 
class Library(gtk.Object):
31
 
    """A Library contains useful objects.
32
 
 
33
 
    The only required method these objects support is a .key
34
 
    attribute, but specific types of libraries may require more
35
 
    advanced interfaces.
36
 
    """
37
 
 
38
 
    SIG_PYOBJECT = (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,))
39
 
    __gsignals__ = {
40
 
        'changed': SIG_PYOBJECT,
41
 
        'removed': SIG_PYOBJECT,
42
 
        'added': SIG_PYOBJECT,
43
 
        }
44
 
    del(SIG_PYOBJECT)
45
 
 
46
 
    librarian = None
47
 
 
48
 
    def __init__(self, name=None):
49
 
        super(Library, self).__init__()
50
 
        self._contents = {}
51
 
        self._masked = {}
52
 
        for key in ['get', 'keys', 'values', 'items', 'iterkeys',
53
 
                    'itervalues', 'iteritems', 'has_key']:
54
 
            setattr(self, key, getattr(self._contents, key))
55
 
        if self.librarian is not None and name is not None:
56
 
            self.librarian.register(self, name)
57
 
 
58
 
    def add(self, items):
59
 
        """Add items. This causes an 'added' signal.
60
 
 
61
 
        Return the list of items actually added, filtering out items
62
 
        already in the library.
63
 
        """
64
 
        items = filter(lambda item: item not in self, items)
65
 
        for item in items:
66
 
            self._contents[item.key] = item
67
 
        self.emit('added', items)
68
 
        return items
69
 
 
70
 
    def remove(self, items):
71
 
        """Remove items. This causes a 'removed' signal."""
72
 
        for item in items:
73
 
            del(self._contents[item.key])
74
 
        self.emit('removed', items)
75
 
 
76
 
    def changed(self, items):
77
 
        """Alert other users that these items have changed.
78
 
 
79
 
        This causes a 'changed' signal. If a librarian is available
80
 
        this function will call its changed method instead, and all
81
 
        libraries that librarian manages may fire a 'changed' signal.
82
 
 
83
 
        The item list may be filtered to those items actually in the
84
 
        library. If a librarian is available, it will handle the
85
 
        filtering instead. That means if this method is delegated to
86
 
        the librarian, this library's changed signal may not fire, but
87
 
        another's might.
88
 
        """
89
 
        if self.librarian and self in self.librarian.libraries.itervalues():
90
 
            self.librarian.changed(items)
91
 
        else:
92
 
            items = filter(self.__contains__, items)
93
 
            self._changed(items)
94
 
 
95
 
    def _changed(self, items):
96
 
        # Called by the changed method and Librarians.
97
 
        self.emit('changed', items)
98
 
 
99
 
    def __iter__(self):
100
 
        """Iterate over the items in the library."""
101
 
        return self._contents.itervalues()
102
 
 
103
 
    def __len__(self):
104
 
        """The number of items in the library."""
105
 
        return len(self._contents)
106
 
 
107
 
    def __getitem__(self, key):
108
 
        """Find a item given its key."""
109
 
        return self._contents[key]
110
 
 
111
 
    def __contains__(self, item):
112
 
        """Check if a key or item is in the library."""
113
 
        try: return item in self._contents or item.key in self._contents
114
 
        except AttributeError: return False
115
 
 
116
 
    def load(self, filename, skip=False):
117
 
        """Load a library from a file, containing a picked list.
118
 
 
119
 
        Loading does not cause added, changed, or removed signals.
120
 
        """
121
 
        try:
122
 
            if os.path.exists(filename):
123
 
                fileobj = file(filename, "rb")
124
 
                try: items = pickle.load(fileobj)
125
 
                except (pickle.PickleError, EnvironmentError):
126
 
                    traceback.print_exc()
127
 
                    try: shutil.copy(filename, filename + ".not-valid")
128
 
                    except EnvironmentError:
129
 
                        traceback.print_exc()
130
 
                    items = []
131
 
                fileobj.close()
132
 
            else: return
133
 
        except EnvironmentError:
134
 
            return
135
 
 
136
 
        if skip:
137
 
            for item in filter(skip, items):
138
 
                self._contents[item.key] = item
139
 
        else:
140
 
            map(self._load, items)
141
 
 
142
 
    def _load(self, item):
143
 
        """Load a item. Return (changed, removed)."""
144
 
        # Subclases should override this if they want to check
145
 
        # item validity; see FileLibrary.
146
 
        self._contents[item.key] = item
147
 
 
148
 
    def save(self, filename):
149
 
        """Save the library to the given filename."""
150
 
        if not os.path.isdir(os.path.dirname(filename)):
151
 
            os.makedirs(os.path.dirname(filename))
152
 
        fileobj = file(filename + ".tmp", "wb")
153
 
        if fcntl is not None:
154
 
            fcntl.flock(fileobj.fileno(), fcntl.LOCK_EX)
155
 
        items = self.values()
156
 
        for masked in self._masked.values():
157
 
            items.extend(masked.values())
158
 
        # Item keys are often based on filenames, in which case
159
 
        # sorting takes advantage of the filesystem cache when we
160
 
        # reload/rescan the files.
161
 
        items.sort(key=lambda item: item.key)
162
 
        pickle.dump(items, fileobj, pickle.HIGHEST_PROTOCOL)
163
 
        os.rename(filename + ".tmp", filename)
164
 
        fileobj.close()
165
 
 
166
 
class Librarian(gtk.Object):
167
 
    """The librarian is a nice interface to all active libraries.
168
 
 
169
 
    Librarians are a kind of meta-library. When any of their
170
 
    registered libraries fire a signal, they fire the same
171
 
    signal. Likewise, they provide various methods equivalent to the
172
 
    ones found in libraries that group the results of the real
173
 
    libraries.
174
 
 
175
 
    Attributes:
176
 
    libraries -- a dict mapping library names to libraries
177
 
    """
178
 
 
179
 
    SIG_PYOBJECT = (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,))
180
 
    __gsignals__ = {
181
 
        'changed': SIG_PYOBJECT,
182
 
        'removed': SIG_PYOBJECT,
183
 
        'added': SIG_PYOBJECT,
184
 
        }
185
 
 
186
 
    def __init__(self):
187
 
        super(Librarian, self).__init__()
188
 
        self.libraries = {}
189
 
        self.__signals = {}
190
 
 
191
 
    def register(self, library, name):
192
 
        """Register a library with this librarian."""
193
 
        if name in self.libraries or name in self.__signals:
194
 
            raise ValueError("library %r is already active" % name)
195
 
 
196
 
        added_sig = library.connect('added', self.__added)
197
 
        removed_sig = library.connect('removed', self.__removed)
198
 
        changed_sig = library.connect('changed', self.__changed)
199
 
        library.connect('destroy', self.__unregister, name)
200
 
        self.libraries[name] = library
201
 
        self.__signals[library] = [added_sig, removed_sig, changed_sig]
202
 
 
203
 
    def __unregister(self, library, name):
204
 
        # This function, unlike register, should be private.
205
 
        # Libraries get unregistered at the discretion of the
206
 
        # librarian, not the libraries.
207
 
        del(self.libraries[name])
208
 
        del(self.__signals[library])
209
 
 
210
 
    # FIXME: We can be smarter about this -- queue a list of items
211
 
    # and fire the signal after a short wait, to take advantage of
212
 
    # a case where many libraries fire a signal at the same time (or
213
 
    # one fires a signal often).
214
 
 
215
 
    def __changed(self, library, items):
216
 
        self.emit('changed', items)
217
 
 
218
 
    def __added(self, library, items):
219
 
        self.emit('added', items)
220
 
 
221
 
    def __removed(self, library, items):
222
 
        self.emit('removed', items)
223
 
 
224
 
    def changed(self, items):
225
 
        """Triage the items and inform their real libraries."""
226
 
        for library in self.libraries.itervalues():
227
 
            in_library = filter(library.__contains__, items)
228
 
            if in_library:
229
 
                library._changed(in_library)
230
 
 
231
 
    def __getitem__(self, key):
232
 
        """Find a item given its key."""
233
 
        for library in self.libraries.itervalues():
234
 
            try: return library[key]
235
 
            except KeyError: pass
236
 
        else: raise KeyError, key
237
 
 
238
 
    def get(self, key, default=None):
239
 
        try: return self[key]
240
 
        except KeyError: return default
241
 
 
242
 
    def remove(self, items):
243
 
        """Remove items from all libraries."""
244
 
        for library in self.libraries.itervalues():
245
 
            library.remove(filter(library.__contains__, items))
246
 
 
247
 
    def __contains__(self, item):
248
 
        """Check if a key or item is in the library."""
249
 
        for library in self.libraries.itervalues():
250
 
            if item in library:
251
 
                return True
252
 
        else: return False
253
 
 
254
 
    def __iter__(self):
255
 
        """Iterate over all items in all libraries."""
256
 
        return itertools.chain(*self.libraries.itervalues())
257
 
 
258
 
    def move(self, items, from_, to):
259
 
        """Move items from one library to another.
260
 
 
261
 
        This causes 'removed' signals on the from library, and 'added'
262
 
        signals on the 'to' library, but will not cause any signals
263
 
        to be emitted via this librarian.
264
 
        """
265
 
        try:
266
 
            from_.handler_block(self.__signals[from_][1])
267
 
            to.handler_block(self.__signals[to][0])
268
 
            from_.remove(items)
269
 
            to.add(items)
270
 
        finally:
271
 
            from_.handler_unblock(self.__signals[from_][1])
272
 
            to.handler_unblock(self.__signals[to][0])
273