1
# Copyright (C) 2009-2012 Canonical, Ltd.
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.
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.
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."""
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
27
from dirspec import basedir
28
from gettext import lgettext as _
30
gettext.bindtextdomain("rhythmbox-ubuntuone", "/usr/share/locale")
31
gettext.textdomain("rhythmbox-ubuntuone")
33
MUSIC_STORE_WIDGET = U1MusicStore()
34
U1LIBRARYPATH = MUSIC_STORE_WIDGET.get_library_location()
37
class U1EntryType(RB.RhythmDBEntryType):
38
"""Entry type for the Ubuntu One Music Store source."""
41
RB.RhythmDBEntryType.__init__(self, name='ubuntuone')
43
def do_can_sync_metadata(self, entry):
44
"""Not a real source, so we can't sync metadata."""
47
def do_sync_metadata(self, entry, changes):
52
class U1MusicStoreWidget(object):
53
"""The Ubuntu One Music Store."""
54
def __init__(self, plugin):
59
self.u1_library_symlink = None
60
self.entry_type = U1EntryType()
62
def activate(self, shell):
64
self.db = shell.get_property("db")
65
group = RB.DisplayPageGroup.get_by_id("stores")
67
icon = Gtk.IconTheme.get_default().load_icon(
70
self.db.register_entry_type(self.entry_type)
72
self.source = GObject.new(U1Source,
74
entry_type=self.entry_type,
77
shell.register_entry_type_for_source(self.source, self.entry_type)
78
shell.append_display_page(self.source, group)
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)
89
def deactivate(self, shell):
90
"""Plugin shutdown."""
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
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":
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):]
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()
126
def download_finished(self, source, path):
127
"""A file is finished downloading"""
128
library_uri = self._udf_path_to_library_uri(path)
130
if not self.shell.props.db.entry_lookup_by_location(library_uri):
131
self.db.add_uri(library_uri)
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)
138
print "couldn't find entry", uri
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()
153
player.play_entry(entry, libsrc)
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)
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()
164
player.play_entry(entry, self.source)
165
# delete this entry when it finishes playing. Don't know how yet.
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,
181
os.symlink(U1LIBRARYPATH, u1_library_path)
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,
192
self.u1_library_symlink = u1_library_path
195
class U1Source(RB.Source):
196
"""A Rhythmbox source widget for the U1 Music Store."""
197
# gproperties required so that rb.Source is instantiable
199
'plugin': (GObject.GObject, 'plugin', 'plugin',
200
GObject.PARAM_WRITABLE | GObject.PARAM_CONSTRUCT_ONLY),
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
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,)),
217
RB.Source.__init__(self, name=_("Ubuntu One"))
218
self.browser = MUSIC_STORE_WIDGET
219
self.__activated = False
221
self.add_music_store_widget()
223
def do_impl_activate(self):
224
"""Source startup."""
227
self.__activated = True
228
RB.Source.do_impl_activate(self)
230
def do_impl_want_uri(self, uri):
231
"""I want to handle u1ms URLs"""
232
if uri.startswith("u1ms://"):
236
def do_impl_add_uri(self, uri, title, genre):
237
"""Handle a u1ms URL"""
238
if not uri.startswith("u1ms://"):
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)
248
def add_music_store_widget(self):
249
"""Display the music store widget in Rhythmbox."""
250
# pylint: disable=E1101
251
self.add(self.browser)
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)
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
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)
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)
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)
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)
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
294
raise AttributeError('unknown property %s' % prop.name)