~ubuntu-branches/ubuntu/precise/rhythmbox-ubuntuone/precise-proposed

« back to all changes in this revision

Viewing changes to ubuntuone/MusicStoreWidget.py

  • Committer: Package Import Robot
  • Author(s): Rodney Dawes
  • Date: 2012-02-03 17:51:00 UTC
  • Revision ID: package-import@ubuntu.com-20120203175100-oethgj2ksx2b1yef
Tags: upstream-2.99.3
ImportĀ upstreamĀ versionĀ 2.99.3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2009-2012 Canonical, Ltd.
 
2
#
 
3
# This library is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU Lesser General Public License
 
5
# version 3.0 as published by the Free Software Foundation.
 
6
#
 
7
# This library is distributed in the hope that it will be useful,
 
8
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
9
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
10
# GNU Lesser General Public License version 3.0 for more details.
 
11
#
 
12
# You should have received a copy of the GNU Lesser General Public
 
13
# License along with this library. If not, see
 
14
# <http://www.gnu.org/licenses/>.
 
15
"""The Ubuntu One Rhythmbox plugin."""
 
16
 
 
17
import gettext
 
18
import os
 
19
import stat
 
20
import urlparse
 
21
 
 
22
# pylint: disable=E0611,F0401
 
23
from gi.repository import Gio, GObject, Gtk, RB
 
24
from gi.repository.UbuntuOneUI import MusicStore as U1MusicStore
 
25
# pylint: enable=E0611,F0401
 
26
 
 
27
from dirspec import basedir
 
28
from gettext import lgettext as _
 
29
 
 
30
gettext.bindtextdomain("rhythmbox-ubuntuone", "/usr/share/locale")
 
31
gettext.textdomain("rhythmbox-ubuntuone")
 
32
 
 
33
MUSIC_STORE_WIDGET = U1MusicStore()
 
34
U1LIBRARYPATH = MUSIC_STORE_WIDGET.get_library_location()
 
35
 
 
36
 
 
37
class U1EntryType(RB.RhythmDBEntryType):
 
38
    """Entry type for the Ubuntu One Music Store source."""
 
39
 
 
40
    def __init__(self):
 
41
        RB.RhythmDBEntryType.__init__(self, name='ubuntuone')
 
42
 
 
43
    def do_can_sync_metadata(self, entry):
 
44
        """Not a real source, so we can't sync metadata."""
 
45
        return False
 
46
 
 
47
    def do_sync_metadata(self, entry, changes):
 
48
        """Do nothing."""
 
49
        return
 
50
 
 
51
 
 
52
class U1MusicStoreWidget(object):
 
53
    """The Ubuntu One Music Store."""
 
54
    def __init__(self, plugin):
 
55
        self.plugin = plugin
 
56
        self.db = None
 
57
        self.shell = None
 
58
        self.source = None
 
59
        self.u1_library_symlink = None
 
60
        self.entry_type = U1EntryType()
 
61
 
 
62
    def activate(self, shell):
 
63
        """Plugin startup."""
 
64
        self.db = shell.get_property("db")
 
65
        group = RB.DisplayPageGroup.get_by_id("stores")
 
66
 
 
67
        icon = Gtk.IconTheme.get_default().load_icon(
 
68
            "ubuntuone", 24, 0)
 
69
 
 
70
        self.db.register_entry_type(self.entry_type)
 
71
 
 
72
        self.source = GObject.new(U1Source,
 
73
                                  shell=shell,
 
74
                                  entry_type=self.entry_type,
 
75
                                  pixbuf=icon,
 
76
                                  plugin=self.plugin)
 
77
        shell.register_entry_type_for_source(self.source, self.entry_type)
 
78
        shell.append_display_page(self.source, group)
 
79
 
 
80
        self.shell = shell
 
81
        self.source.connect("preview-mp3", self.play_preview_mp3)
 
82
        self.source.connect("play-library", self.play_library)
 
83
        self.source.connect("download-finished", self.download_finished)
 
84
        self.source.connect("url-loaded", self.url_loaded)
 
85
 
 
86
        # Do these every time
 
87
        self.add_u1_library()
 
88
 
 
89
    def deactivate(self, shell):
 
90
        """Plugin shutdown."""
 
91
        # remove source
 
92
        # FIXME?: This API changed, but doesn't seem entirely necessary.
 
93
        #         It's not entirely clear which API to use for this now.
 
94
        #self.source.delete_thyself()
 
95
        # delete held references
 
96
        del self.db
 
97
        del self.source
 
98
        del self.shell
 
99
 
 
100
    def url_loaded(self, source, url):
 
101
        """A URL is loaded in the plugin"""
 
102
        print "URL loaded:", url
 
103
        if urlparse.urlparse(url)[2] == "https":
 
104
            pass
 
105
        else:
 
106
            pass
 
107
 
 
108
    def _udf_path_to_library_uri(self, path):
 
109
        """Calculate the path in the library.
 
110
        Since the library is accessed via the created symlink, but the path
 
111
        passed to us is to a file in the actual music store UDF, we need to
 
112
        work out the path inside the library, i.e., what the path to that file
 
113
        is via the symlink."""
 
114
        if path.startswith(U1LIBRARYPATH):
 
115
            subpath = path[len(U1LIBRARYPATH):]
 
116
        else:
 
117
            subpath = path
 
118
        if subpath.startswith("/"):
 
119
            subpath = subpath[1:]
 
120
        library_path = os.path.join(self.u1_library_symlink, subpath)
 
121
        # convert path to URI. Don't use urllib for this; Python and
 
122
        # glib escape URLs differently. gio does it the glib way.
 
123
        library_uri = Gio.File.new_for_path(library_path).get_uri()
 
124
        return library_uri
 
125
 
 
126
    def download_finished(self, source, path):
 
127
        """A file is finished downloading"""
 
128
        library_uri = self._udf_path_to_library_uri(path)
 
129
        # Import the URI
 
130
        if not self.shell.props.db.entry_lookup_by_location(library_uri):
 
131
            self.db.add_uri(library_uri)
 
132
 
 
133
    def play_library(self, source, path):
 
134
        """Switch to and start playing a song from the library"""
 
135
        uri = self._udf_path_to_library_uri(path)
 
136
        entry = self.shell.props.db.entry_lookup_by_location(uri)
 
137
        if not entry:
 
138
            print "couldn't find entry", uri
 
139
            return
 
140
        libsrc = self.shell.props.library_source
 
141
        artist_view, album_view = libsrc.get_property_views()[0:2]
 
142
        song_view = libsrc.get_entry_view()
 
143
        artist = self.shell.props.db.entry_get(entry,
 
144
                                               RB.RhythmDBPropType.ARTIST)
 
145
        album = self.shell.props.db.entry_get(entry,
 
146
                                              RB.RhythmDBPropType.ALBUM)
 
147
        self.shell.props.display_page_tree.select(libsrc)
 
148
        artist_view.set_selection([artist])
 
149
        album_view.set_selection([album])
 
150
        song_view.scroll_to_entry(entry)
 
151
        player = self.shell.get_player()
 
152
        player.stop()
 
153
        player.play_entry(entry, libsrc)
 
154
 
 
155
    def play_preview_mp3(self, source, url, title):
 
156
        """Play a passed mp3; signal handler for preview-mp3 signal."""
 
157
        # create an entry, don't save it, and play it
 
158
        entry = self.shell.props.db.entry_lookup_by_location(url)
 
159
        if entry is None:
 
160
            entry = self.shell.props.db.entry_new(self.entry_type, url)
 
161
        self.shell.props.db.set(entry, RB.RhythmDBPropType.TITLE, title)
 
162
        player = self.shell.get_player()
 
163
        player.stop()
 
164
        player.play_entry(entry, self.source)
 
165
        # delete this entry when it finishes playing. Don't know how yet.
 
166
 
 
167
    def add_u1_library(self):
 
168
        """Add the U1 library if not listed in RB and re-add if changed."""
 
169
        u1_library_path_folder = basedir.save_data_path("ubuntuone")
 
170
        # Ensure that we can write to the folder, because syncdaemon creates it
 
171
        # with no write permissions
 
172
        os.chmod(u1_library_path_folder,
 
173
            os.stat(u1_library_path_folder)[stat.ST_MODE] | stat.S_IWUSR)
 
174
        # Translators: this is the name under Music for U1 music in Rhythmbox
 
175
        u1_library_path = os.path.join(u1_library_path_folder,
 
176
                                       _("Purchased from Ubuntu One"))
 
177
        if not os.path.islink(u1_library_path):
 
178
            if not os.path.exists(u1_library_path):
 
179
                print "Attempting to symlink %s to %s" % (U1LIBRARYPATH,
 
180
                                                          u1_library_path)
 
181
                os.symlink(U1LIBRARYPATH, u1_library_path)
 
182
            else:
 
183
                # something that isn't a symlink already exists. That's not
 
184
                # supposed to happen.
 
185
                # Write a warning and carry on.
 
186
                print ("Warning: library location %s existed. It should have "
 
187
                    "been a symlink to %s, and it wasn't. This isn't supposed "
 
188
                    "to happen. Carrying on anyway, on the assumption that "
 
189
                    "you know what you're doing. If this is a problem, then "
 
190
                    "delete or rename %s.") % (u1_library_path, U1LIBRARYPATH,
 
191
                    u1_library_path)
 
192
        self.u1_library_symlink = u1_library_path
 
193
 
 
194
 
 
195
class U1Source(RB.Source):
 
196
    """A Rhythmbox source widget for the U1 Music Store."""
 
197
    # gproperties required so that rb.Source is instantiable
 
198
    __gproperties__ = {
 
199
      'plugin': (GObject.GObject, 'plugin', 'plugin',
 
200
                 GObject.PARAM_WRITABLE | GObject.PARAM_CONSTRUCT_ONLY),
 
201
      }
 
202
    # we have the preview-mp3 signal; we receive it from the widget, and
 
203
    # re-emit it so that the plugin gets it, because the plugin actually
 
204
    # plays the mp3
 
205
    __gsignals__ = {
 
206
      "preview-mp3": (GObject.SIGNAL_RUN_FIRST,
 
207
                      GObject.TYPE_NONE, (str, str)),
 
208
      "play-library": (GObject.SIGNAL_RUN_FIRST,
 
209
                       GObject.TYPE_NONE, (str,)),
 
210
      "download-finished": (GObject.SIGNAL_RUN_FIRST,
 
211
                            GObject.TYPE_NONE, (str,)),
 
212
      "url-loaded": (GObject.SIGNAL_RUN_FIRST,
 
213
                     GObject.TYPE_NONE, (str,)),
 
214
    }
 
215
 
 
216
    def __init__(self):
 
217
        RB.Source.__init__(self, name=_("Ubuntu One"))
 
218
        self.browser = MUSIC_STORE_WIDGET
 
219
        self.__activated = False
 
220
        self.__plugin = None
 
221
        self.add_music_store_widget()
 
222
 
 
223
    def do_impl_activate(self):
 
224
        """Source startup."""
 
225
        if self.__activated:
 
226
            return
 
227
        self.__activated = True
 
228
        RB.Source.do_impl_activate(self)
 
229
 
 
230
    def do_impl_want_uri(self, uri):
 
231
        """I want to handle u1ms URLs"""
 
232
        if uri.startswith("u1ms://"):
 
233
            return 100
 
234
        return 0
 
235
 
 
236
    def do_impl_add_uri(self, uri, title, genre):
 
237
        """Handle a u1ms URL"""
 
238
        if not uri.startswith("u1ms://"):
 
239
            return False
 
240
        uri_to_use = uri.replace("u1ms://", "http://")
 
241
        print "Calling u1musicstore plugin with %s" % uri_to_use
 
242
        # pylint: disable=E1101
 
243
        shell = self.get_property("shell")
 
244
        shell.props.display_page_tree.select(self)
 
245
        self.browser.load_store_link(uri_to_use)
 
246
        return True
 
247
 
 
248
    def add_music_store_widget(self):
 
249
        """Display the music store widget in Rhythmbox."""
 
250
        # pylint: disable=E1101
 
251
        self.add(self.browser)
 
252
        self.browser.show()
 
253
        self.show()
 
254
        self.browser.set_property("visible", True)
 
255
        self.browser.connect("preview-mp3",
 
256
                             self.re_emit_preview)
 
257
        self.browser.connect("play-library",
 
258
                             self.re_emit_playlibrary)
 
259
        self.browser.connect("download-finished",
 
260
                             self.re_emit_downloadfinished)
 
261
        self.browser.connect("url-loaded",
 
262
                             self.re_emit_urlloaded)
 
263
 
 
264
    def do_impl_can_pause(self):
 
265
        """Implementation can pause.
 
266
           If we don't handle this, Rhythmbox segfaults."""
 
267
        return True  # so we can pause, else we segfault
 
268
 
 
269
    def re_emit_preview(self, widget, url, title):
 
270
        """Handle the preview-mp3 signal and re-emit it."""
 
271
        # pylint: disable=E1101
 
272
        self.emit("preview-mp3", url, title)
 
273
 
 
274
    def re_emit_playlibrary(self, widget, path):
 
275
        """Handle the play-library signal and re-emit it."""
 
276
        # pylint: disable=E1101
 
277
        self.emit("play-library", path)
 
278
 
 
279
    def re_emit_downloadfinished(self, widget, path):
 
280
        """Handle the download-finished signal and re-emit it."""
 
281
        # pylint: disable=E1101
 
282
        self.emit("download-finished", path)
 
283
 
 
284
    def re_emit_urlloaded(self, widget, url):
 
285
        """Handle the url-loaded signal and re-emit it."""
 
286
        # pylint: disable=E1101
 
287
        self.emit("url-loaded", url)
 
288
 
 
289
    def do_set_property(self, prop, value):
 
290
        """Allow property settings to handle the plug-in call."""
 
291
        if prop.name == 'plugin':
 
292
            self.__plugin = value
 
293
        else:
 
294
            raise AttributeError('unknown property %s' % prop.name)