~ubuntu-branches/ubuntu/maverick/rhythmbox-ubuntuone-music-store/maverick

« back to all changes in this revision

Viewing changes to umusicstore/MusicStoreWidget.py

  • Committer: Bazaar Package Importer
  • Author(s): Rodrigo Moya
  • Date: 2010-07-21 16:03:11 UTC
  • mfrom: (19.1.5 upstream)
  • Revision ID: james.westby@ubuntu.com-20100721160311-xxsw20h8eymow5pu
Tags: 0.1.1-0ubuntu1
* New upstream release:
  - Add music links (Stuart Langridge)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""The Ubuntu One Music Store Rhythmbox plugin."""
 
2
# Copyright (C) 2009 Canonical, Ltd.
 
3
#
 
4
# This library is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU Lesser General Public License
 
6
# version 3.0 as published by the Free Software Foundation.
 
7
#
 
8
# This library is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU Lesser General Public License version 3.0 for more details.
 
12
#
 
13
# You should have received a copy of the GNU Lesser General Public
 
14
# License along with this library. If not, see
 
15
# <http://www.gnu.org/licenses/>.
 
16
#
 
17
# Authored by Stuart Langridge <stuart.langridge@canonical.com>
 
18
 
 
19
 
 
20
import gtk, gobject, os, urllib, gconf, stat, urlparse, gio
 
21
import gst, gst.pbutils
 
22
import aptdaemon.client
 
23
from aptdaemon.enums import *
 
24
from aptdaemon.gtkwidgets import AptErrorDialog, \
 
25
                                 AptProgressBar
 
26
import rb, rhythmdb
 
27
from ubuntuone.gtkwidgets import MusicStore as U1MusicStore
 
28
import xdg.BaseDirectory
 
29
import dbus.exceptions
 
30
 
 
31
import gettext
 
32
from gettext import lgettext as _
 
33
gettext.bindtextdomain("rhythmbox-ubuntuone-music-store", "/usr/share/locale")
 
34
gettext.textdomain("rhythmbox-ubuntuone-music-store")
 
35
 
 
36
MUSIC_STORE_WIDGET = U1MusicStore() # keep this around for later
 
37
U1LIBRARYPATH = MUSIC_STORE_WIDGET.get_library_location()
 
38
RB_LIBRARY_LOCATIONS = "/apps/rhythmbox/library_locations"
 
39
 
 
40
class U1MusicStoreWidget(object):
 
41
    """The Ubuntu One Music Store."""
 
42
    def __init__(self, plugin, find_file):
 
43
        self.plugin = plugin
 
44
        self.find_file = find_file
 
45
    
 
46
    def activate(self, shell):
 
47
        """Plugin startup."""
 
48
        self.db = shell.get_property("db")
 
49
        group = rb.rb_source_group_get_by_name ("stores")
 
50
        if not group:
 
51
            group = rb.rb_source_group_register ("stores",
 
52
                                           "Stores",
 
53
                                           rb.SOURCE_GROUP_CATEGORY_FIXED)
 
54
        
 
55
        icon_file_name = self.find_file("musicstore_icon.png")
 
56
        icon = gtk.gdk.pixbuf_new_from_file_at_size(icon_file_name, 22, 22)
 
57
        self.entry_type = self.db.entry_register_type("U1EntryType")
 
58
        self.source = gobject.new (U1Source,
 
59
                                   shell=shell,
 
60
                                   entry_type=self.entry_type,
 
61
                                   source_group=group,
 
62
                                   icon=icon,
 
63
                                   plugin=self.plugin)
 
64
        shell.register_entry_type_for_source(self.source, self.entry_type)
 
65
        self.shell = shell
 
66
        self.source.connect("preview-mp3", self.play_preview_mp3)
 
67
        self.source.connect("play-library", self.play_library)
 
68
        self.source.connect("download-finished", self.download_finished)
 
69
        self.source.connect("url-loaded", self.url_loaded)
 
70
            
 
71
        # Do these every time
 
72
        shell.append_source(self.source, None) # Add the source to the list
 
73
        self.add_u1_library()
 
74
 
 
75
    def deactivate(self, shell):
 
76
        """Plugin shutdown."""
 
77
        # remove source
 
78
        self.source.delete_thyself()
 
79
        # remove the library, if it's empty
 
80
        try:
 
81
            filecount = len(os.listdir(self.U1_LIBRARY_SYMLINK))
 
82
        except OSError:
 
83
            # symlink is dangling
 
84
            # so they never downloaded anything
 
85
            filecount = 0
 
86
        if filecount == 0:
 
87
            client = gconf.client_get_default()
 
88
            libraries = client.get_list(RB_LIBRARY_LOCATIONS, gconf.VALUE_STRING)
 
89
            if self.u1_library_path_url in libraries:
 
90
                client.notify_remove(self.library_adder)
 
91
                libraries.remove(self.u1_library_path_url)
 
92
                client.set_list(RB_LIBRARY_LOCATIONS, gconf.VALUE_STRING, libraries)
 
93
        # delete held references
 
94
        del self.db
 
95
        del self.source
 
96
        del self.shell
 
97
 
 
98
    def url_loaded(self, source, url):
 
99
        """A URL is loaded in the plugin"""
 
100
        print "URL loaded:", url
 
101
        if urlparse.urlparse(url).scheme == "https":
 
102
            pass
 
103
        else:
 
104
            pass
 
105
    
 
106
    def _udf_path_to_library_uri(self, path):
 
107
        """Calculate the path in the library. 
 
108
        Since the library is accessed via the created symlink, but the path 
 
109
        passed to us is to a file in the actual music store UDF, we need to 
 
110
        work out the path inside the library, i.e., what the path to that file
 
111
        is via the symlink."""
 
112
        
 
113
        if path.startswith(U1LIBRARYPATH):
 
114
            subpath = path[len(U1LIBRARYPATH):]
 
115
        else:
 
116
            subpath = path
 
117
        if subpath.startswith("/"): subpath = subpath[1:]
 
118
        library_path = os.path.join(self.U1_LIBRARY_SYMLINK, subpath)
 
119
        # convert path to URI. Don't use urllib for this; Python and
 
120
        # glib escape URLs differently. gio does it the glib way.
 
121
        library_uri = gio.File(library_path).get_uri()
 
122
        return library_uri
 
123
 
 
124
    def download_finished(self, source, path):
 
125
        """A file is finished downloading"""
 
126
        library_uri = self._udf_path_to_library_uri(path)
 
127
        # Import the URI
 
128
        if not self.shell.props.db.entry_lookup_by_location(library_uri):
 
129
            self.db.add_uri(library_uri)
 
130
 
 
131
    def play_library(self, source, path):
 
132
        """Switch to and start playing a song from the library"""
 
133
        uri = self._udf_path_to_library_uri(path)
 
134
        entry = self.shell.props.db.entry_lookup_by_location(uri)
 
135
        if not entry: 
 
136
            print "couldn't find entry", uri
 
137
            return
 
138
        libsrc = self.shell.props.library_source
 
139
        genre_view, artist_view, album_view = libsrc.get_property_views()
 
140
        song_view = libsrc.get_entry_view()
 
141
        artist = self.shell.props.db.entry_get(entry, rhythmdb.PROP_ARTIST)
 
142
        album = self.shell.props.db.entry_get(entry, rhythmdb.PROP_ALBUM)
 
143
        self.shell.props.sourcelist.select(libsrc)
 
144
        artist_view.set_selection([artist])
 
145
        album_view.set_selection([album])
 
146
        song_view.scroll_to_entry(entry)
 
147
        player = self.shell.get_player()
 
148
        player.stop()
 
149
        player.play()
 
150
        player.play_entry(entry, libsrc)
 
151
 
 
152
    def play_preview_mp3(self, source, url, title):
 
153
        """Play a passed mp3; signal handler for preview-mp3 signal."""
 
154
        # create an entry, don't save it, and play it
 
155
        entry = self.shell.props.db.entry_lookup_by_location(url)
 
156
        if entry is None:
 
157
            entry = self.shell.props.db.entry_new(self.entry_type, url)
 
158
        self.shell.props.db.set(entry, rhythmdb.PROP_TITLE, title)
 
159
        player = self.shell.get_player()
 
160
        player.stop()
 
161
        player.play()
 
162
        player.play_entry(entry, self.source)
 
163
        # FIXME delete this entry when it finishes playing. Don't know how yet.
 
164
 
 
165
    def add_u1_library(self):
 
166
        """Add the U1 library if not listed in RB and re-add if changed."""
 
167
        u1_library_path_folder = xdg.BaseDirectory.save_data_path("ubuntuone")
 
168
        # Ensure that we can write to the folder, because syncdaemon creates it
 
169
        # with no write permissions
 
170
        os.chmod(u1_library_path_folder,
 
171
            os.stat(u1_library_path_folder)[stat.ST_MODE] | stat.S_IWUSR)
 
172
        # Translators: this is the name under Music for U1 music in Rhythmbox
 
173
        u1_library_path = os.path.join(u1_library_path_folder, _("Purchased from Ubuntu One"))
 
174
        if not os.path.islink(u1_library_path):
 
175
            if not os.path.exists(u1_library_path):
 
176
                print "Attempting to symlink %s to %s" % (U1LIBRARYPATH, 
 
177
                    u1_library_path)
 
178
                os.symlink(U1LIBRARYPATH, u1_library_path)
 
179
            else:
 
180
                # something that isn't a symlink already exists. That's not
 
181
                # supposed to happen.
 
182
                # Write a warning and carry on.
 
183
                print ("Warning: library location %s existed. It should have "
 
184
                    "been a symlink to %s, and it wasn't. This isn't supposed "
 
185
                    "to happen. Carrying on anyway, on the assumption that "
 
186
                    "you know what you're doing. If this is a problem, then "
 
187
                    "delete or rename %s.") % (u1_library_path, U1LIBRARYPATH,
 
188
                    u1_library_path)
 
189
        self.u1_library_path_url = "file://%s" % urllib.quote(u1_library_path)
 
190
        self.U1_LIBRARY_SYMLINK = u1_library_path
 
191
        client = gconf.client_get_default()
 
192
        self._add_u1_library_if_not_present(client)
 
193
        self._remove_old_u1_library_if_present(client)
 
194
        # Watch for changes to the gconf key and re-add the library
 
195
        self.library_adder = client.notify_add(RB_LIBRARY_LOCATIONS, 
 
196
            self._add_u1_library_if_not_present)
 
197
 
 
198
    def _add_u1_library_if_not_present(self, client, *args, **kwargs):
 
199
        """Check for the U1 library and add to libraries list."""
 
200
        libraries = client.get_list(RB_LIBRARY_LOCATIONS, gconf.VALUE_STRING)
 
201
        if self.u1_library_path_url not in libraries:
 
202
            libraries.append(self.u1_library_path_url)
 
203
            client.set_list(RB_LIBRARY_LOCATIONS, gconf.VALUE_STRING, libraries)
 
204
 
 
205
    def _remove_old_u1_library_if_present(self, client, *args, **kwargs):
 
206
        """Check for the old U1 library and remove it from libraries list."""
 
207
        libraries = client.get_list(RB_LIBRARY_LOCATIONS, gconf.VALUE_STRING)
 
208
        removed = False
 
209
        # originally, this was the library path. Someone might have this.
 
210
        old_path = "file://%s" % os.path.expanduser("~/.ubuntuone/musicstore")
 
211
        if old_path in libraries:
 
212
            libraries.remove(old_path)
 
213
            removed = True
 
214
        # Then, this was the library path, which we put into gconf unescaped
 
215
        actual_udf_path_unescaped = "file://%s" % U1LIBRARYPATH
 
216
        if actual_udf_path_unescaped in libraries:
 
217
            libraries.remove(actual_udf_path_unescaped)
 
218
            removed = True
 
219
        # In theory, no-one should have the escaped path, but let's check
 
220
        actual_udf_path_escaped = "file://%s" % U1LIBRARYPATH
 
221
        if actual_udf_path_escaped in libraries:
 
222
            libraries.remove(actual_udf_path_escaped)
 
223
            removed = True
 
224
        # Also, remove any library which is in .local/share and *isn't*
 
225
        # the current library. This caters for people who got the library
 
226
        # created under one name (say, English) and then had it created
 
227
        # under another (say, after a translation to Dutch)
 
228
        u1_library_path_folder = xdg.BaseDirectory.save_data_path("ubuntuone")
 
229
        u1_library_path_folder_url = "file://%s" % urllib.quote(u1_library_path_folder)
 
230
        symlink_url = "file://" + urllib.quote(self.U1_LIBRARY_SYMLINK)
 
231
        for l in libraries:
 
232
            if l.startswith(u1_library_path_folder_url) and l != symlink_url:
 
233
                libraries.remove(l)
 
234
                # and delete the symlink itself
 
235
                symlink_to_remove = urllib.unquote(l[7:])
 
236
                os.unlink(symlink_to_remove)
 
237
                removed = True
 
238
        if removed:
 
239
            client.set_list(RB_LIBRARY_LOCATIONS, gconf.VALUE_STRING, libraries)
 
240
 
 
241
class U1Source(rb.Source):
 
242
    """A Rhythmbox source widget for the U1 Music Store."""
 
243
    # gproperties required so that rb.Source is instantiable
 
244
    __gproperties__ = {
 
245
      'plugin': (rb.Plugin, 'plugin', 'plugin', 
 
246
                    gobject.PARAM_WRITABLE|gobject.PARAM_CONSTRUCT_ONLY),
 
247
    }
 
248
    # we have the preview-mp3 signal; we receive it from the widget, and re-emit
 
249
    # it so that the plugin gets it, because the plugin actually plays the mp3
 
250
    __gsignals__ = {
 
251
      "preview-mp3": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (str, str)),
 
252
      "play-library": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (str,)),
 
253
      "download-finished": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (str,)),
 
254
      "url-loaded": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (str,)),
 
255
    }
 
256
    
 
257
    def __init__(self):
 
258
        rb.Source.__init__(self, name=_("Ubuntu One"))
 
259
        self.browser = MUSIC_STORE_WIDGET
 
260
        self.__activated = False
 
261
 
 
262
    def do_impl_activate(self):
 
263
        """Source startup."""
 
264
        if self.__activated: return
 
265
        self.__activated = True
 
266
        self.test_can_play_mp3()
 
267
        rb.Source.do_impl_activate (self)
 
268
    
 
269
    def do_impl_want_uri(self, uri):
 
270
        """I want to handle u1ms URLs"""
 
271
        if uri.startswith("u1ms://"):
 
272
            return 100
 
273
        rb.Source.do_impl_want_uri(self, uri)
 
274
        return 0
 
275
 
 
276
    def do_impl_add_uri(self, uri, title, genre):
 
277
        """Handle a u1ms URL"""
 
278
        if not uri.startswith("u1ms://"):
 
279
            return False
 
280
        uri_to_use = uri.replace("u1ms://", "http://")
 
281
        print "Calling u1musicstore plugin with %s" % uri_to_use
 
282
        self.browser.load_store_link(uri_to_use)
 
283
        return True
 
284
 
 
285
    def test_can_play_mp3(self):
 
286
        """Can the user play mp3s? Start a GStreamer pipeline to check."""
 
287
        mp3pth = os.path.realpath(os.path.join(
 
288
            os.path.split(__file__)[0], "empty.mp3"))
 
289
        uri = "file://%s" % urllib.quote("%s" % mp3pth)
 
290
        self.install_pipeline = gst.parse_launch(
 
291
          'uridecodebin uri=%s ! fakesink' % uri)
 
292
        bus = self.install_pipeline.get_bus()
 
293
        bus.add_signal_watch()
 
294
        bus.connect("message::element", self._got_element_msg)
 
295
        bus.connect("message::eos", self._got_end_of_stream)
 
296
        self.install_pipeline.set_state(gst.STATE_PLAYING)
 
297
 
 
298
    def _got_element_msg(self, bus, msg):
 
299
        """Handler for element messages from the check-mp3 pipeline.
 
300
           GStreamer throws a "plugin-missing" element message if the
 
301
           user does not have the right codecs to play a file."""
 
302
        plugin_missing = gst.pbutils.is_missing_plugin_message(msg)
 
303
        if plugin_missing:
 
304
            self.install_pipeline.set_state(gst.STATE_NULL)
 
305
            self.install_mp3_playback()
 
306
 
 
307
    def _got_end_of_stream(self, bus, msg):
 
308
        """Handler for end of stream from the check-mp3 pipeline.
 
309
           If we reach the end of the stream, mp3 playback is enabled."""
 
310
        self.install_pipeline.set_state(gst.STATE_NULL)
 
311
        if os.environ.has_key("U1INSTALLMP3ANYWAY"):
 
312
            # override the decision not to install the package
 
313
            self.install_mp3_playback()
 
314
        else:
 
315
            self.add_music_store_widget()
 
316
 
 
317
    def install_mp3_playback(self):
 
318
        """Use aptdaemon to install the Fluendo mp3 playback codec package."""
 
319
        self.install_box = gtk.Alignment(xscale=0.0, yscale=0.0, xalign=0.5,
 
320
            yalign=0.5)
 
321
        self.install_vbox = gtk.VBox()
 
322
        self.install_label_head = gtk.Label()
 
323
        self.install_label_head.set_use_markup(True)
 
324
        not_installed = _("MP3 plugins are not installed")
 
325
        self.install_label_head.set_markup('<span weight="bold" size="larger">'
 
326
            '%s</span>' % not_installed)
 
327
        self.install_label_head.set_alignment(0.0, 0.5)
 
328
        self.install_label_body = gtk.Label()
 
329
        self.install_label_body.set_text(_('To listen to your purchased songs'
 
330
            ', you need to install MP3 plugins. Click below to install them.'))
 
331
        self.install_label_body.set_alignment(0.0, 0.5)
 
332
        self.install_label_eula = gtk.Label()
 
333
        # EULA text copied from /var/lib/dpkg/info/gstreamer0.10-fluendo-plugins-mp3-partner.templates
 
334
        # The partner package shows the EULA itself on installations, but
 
335
        #  aptdaemon installations work like DEBIAN_FRONTEND=noninteractive
 
336
        # so we show the EULA here. (This also avoids a popup window.)
 
337
        # EULA text is not translatable; do not wrap it with gettext!
 
338
        self.install_label_eula.set_markup(
 
339
            "<small>MPEG Layer-3 audio decoding technology notice\n"
 
340
            "MPEG Layer-3 audio decoding technology licensed "
 
341
            "from Fraunhofer IIS and Thomson\n"
 
342
            "This product cannot be installed in product other than Personal "
 
343
            "Computers sold for general purpose usage, and not for set-top "
 
344
            "boxes, embedded PC, PC which are sold and customized for "
 
345
            "mainly audio or multimedia playback and/or registration, "
 
346
            "unless the seller has received a license by Fraunhofer IIS"
 
347
            "and Thomson and pay the relevant royalties to them.</small>")
 
348
        self.install_label_eula.set_alignment(0.0, 0.5)
 
349
        self.install_label_eula.set_size_request(400,200)
 
350
        self.install_label_eula.set_line_wrap(True)
 
351
        self.install_hbtn = gtk.HButtonBox()
 
352
        self.install_hbtn.set_layout(gtk.BUTTONBOX_END)
 
353
        self.install_button = gtk.Button(label=_("Install MP3 plugins"))
 
354
        self.install_button.connect("clicked", self._start_mp3_install)
 
355
        self.install_hbtn.add(self.install_button)
 
356
        self.install_vbox.pack_start(self.install_label_head, expand=False)
 
357
        self.install_vbox.pack_start(self.install_label_body, expand=False,
 
358
            padding=12)
 
359
        self.install_vbox.pack_start(self.install_hbtn, expand=False)
 
360
        self.install_vbox.pack_start(self.install_label_eula, expand=False,
 
361
            padding=12)
 
362
        self.install_box.add(self.install_vbox)
 
363
        self.install_box.show_all()
 
364
        self.add(self.install_box)
 
365
 
 
366
    def _start_mp3_install(self, btn):
 
367
        """Add the 'partner' repository and update the package list from it."""
 
368
        self.ac = aptdaemon.client.AptClient()
 
369
        try:
 
370
            self.ac.add_repository("deb","http://archive.canonical.com/", "lucid", ["partner"])
 
371
        except dbus.exceptions.DBusException, e:
 
372
            if e.get_dbus_name() == "org.freedesktop.PolicyKit.Error.NotAuthorized":
 
373
                # user cancelled, so exit from here so they can press the button again
 
374
                return
 
375
        self.ac.update_cache(reply_handler=self._finish_updating_packages,
 
376
                        error_handler=self._on_error)
 
377
    
 
378
    def _finish_updating_packages(self, transaction):
 
379
        """Now that partner is added, install our mp3 codec package."""
 
380
        self.update_progress = AptProgressBar(transaction)
 
381
        self.update_progress.show()
 
382
        self.install_label_head.set_text("")
 
383
        self.install_label_body.set_text(_("Finding MP3 plugins"))
 
384
        self.install_label_eula.hide()
 
385
        self.install_hbtn.hide()
 
386
        self.install_vbox.pack_start(self.update_progress, expand=False)
 
387
        transaction.run(reply_handler=lambda: True, 
 
388
                        error_handler=self._on_error)
 
389
        self.ac.install_packages(["gstreamer0.10-fluendo-plugins-mp3-partner"],
 
390
                        reply_handler=self._run_transaction,
 
391
                        error_handler=self._on_error)
 
392
 
 
393
    def _run_transaction(self, transaction):
 
394
        """Show progress of aptdaemon package installation."""
 
395
        self.update_progress.hide()
 
396
        self.install_progress = AptProgressBar(transaction)
 
397
        self.install_progress.show()
 
398
        self.install_label_head.set_text("")
 
399
        self.install_label_body.set_text(_("Installing MP3 plugins"))
 
400
        self.install_label_eula.hide()
 
401
        self.install_hbtn.hide()
 
402
        self.install_vbox.pack_start(self.install_progress, expand=False)
 
403
        transaction.run(reply_handler=lambda: True, 
 
404
                        error_handler=self._on_error)
 
405
        transaction.connect("finished", self._finished)
 
406
 
 
407
    def _finished(self, trans, exit_code):
 
408
        """Aptdaemon package installation finished; show music store."""
 
409
        if exit_code == 0 or exit_code == 2: # 0: success, 2: already installed
 
410
            self.remove(self.install_box)
 
411
            gst.update_registry()
 
412
            self.add_music_store_widget()
 
413
        else:
 
414
            self._on_error("Could not find the "
 
415
                "gstreamer0.10-fluendo-plugins-mp3-partner package.")
 
416
 
 
417
    def _on_error(self, error):
 
418
        """Error handler for aptdaemon."""
 
419
        print error
 
420
        problem_installing = _("There was a problem installing, sorry")
 
421
        self.install_label_head.set_markup('<span weight="bold" size="larger">'
 
422
            '%s</span>' % problem_installing)
 
423
        self.install_label_body.set_text(_('Check your internet connection and '
 
424
            'try again.'))
 
425
        if getattr(self, "install_progress"):
 
426
            self.install_progress.hide()
 
427
        self.install_hbtn.show()
 
428
 
 
429
    def add_music_store_widget(self):
 
430
        """Display the music store widget in Rhythmbox."""
 
431
        self.add(self.browser)
 
432
        self.show_all()
 
433
        self.browser.set_no_show_all(True)
 
434
        self.browser.set_property("visible", True)
 
435
        self.browser.connect("preview-mp3", self.re_emit_preview)
 
436
        self.browser.connect("play-library", self.re_emit_playlibrary)
 
437
        self.browser.connect("download-finished", self.re_emit_downloadfinished)
 
438
        self.browser.connect("url-loaded", self.re_emit_urlloaded)
 
439
 
 
440
    def do_impl_can_pause(self): 
 
441
        """Implementation can pause.
 
442
           If we don't handle this, Rhythmbox segfaults."""
 
443
        return True # so we can pause, else we segfault
 
444
 
 
445
    def re_emit_preview(self, widget, url, title):
 
446
        """Handle the preview-mp3 signal and re-emit it to the rb.Plugin."""
 
447
        self.emit("preview-mp3", url, title)
 
448
 
 
449
    def re_emit_playlibrary(self, widget, path):
 
450
        """Handle the play-library signal and re-emit it to the rb.Plugin."""
 
451
        self.emit("play-library", path)
 
452
 
 
453
    def re_emit_downloadfinished(self, widget, path):
 
454
        """Handle the download-finished signal and re-emit it to the rb.Plugin."""
 
455
        self.emit("download-finished", path)
 
456
 
 
457
    def re_emit_urlloaded(self, widget, url):
 
458
        """Handle the url-loaded signal and re-emit it to the rb.Plugin."""
 
459
        self.emit("url-loaded", url)
 
460
 
 
461
    def do_set_property(self, property, value):
 
462
        """Allow property settings to handle the plugin call."""
 
463
        if property.name == 'plugin':
 
464
            self.__plugin = value
 
465
        else:
 
466
            raise AttributeError, 'unknown property %s' % property.name
 
467
 
 
468