26
26
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
34
import gnomekeyring as keyring
37
37
from gi.repository import RB
38
from gi.repository import GObject, Gtk, Gio
39
# XXX use GnomeKeyring when introspection is available
38
from gi.repository import GObject, Gtk, Gdk, Gio
41
40
from TrackListHandler import TrackListHandler
42
from BuyAlbumHandler import BuyAlbumHandler, MagnatunePurchaseError
41
from DownloadAlbumHandler import DownloadAlbumHandler, MagnatuneDownloadError
42
import MagnatuneAccount
45
45
gettext.install('rhythmbox', RB.locale_dir())
144
141
def do_impl_can_delete(self):
147
def do_impl_pack_paned(self, paned):
144
def do_pack_content(self, content):
148
145
self.__paned_box = Gtk.VBox(homogeneous=False, spacing=5)
149
146
self.pack_start(self.__paned_box, True, True, 0)
150
self.__paned_box.pack_start(paned, True, True, 0)
147
self.__paned_box.pack_start(content, True, True, 0)
153
150
def do_delete_thyself(self):
181
178
for tr in tracks:
182
sku = self.__sku_dict[self.__db.entry_get_string(tr, RB.RhythmDBPropType.LOCATION)]
179
sku = self.__sku_dict[tr.get_string(RB.RhythmDBPropType.LOCATION)]
183
180
url = self.__home_dict[sku]
184
181
if url not in urls:
185
182
Gtk.show_uri(screen, url, Gdk.CURRENT_TIME)
188
def purchase_redirect(self):
185
def download_redirect(self):
189
186
screen = self.props.shell.props.window.get_screen()
190
187
tracks = self.get_entry_view().get_selected_entries()
193
190
for tr in tracks:
194
sku = self.__sku_dict[self.__db.entry_get_string(tr, RB.RhythmDBPropType.LOCATION)]
191
sku = self.__sku_dict[tr.get_string(RB.RhythmDBPropType.LOCATION)]
195
192
url = magnatune_buy_album_uri + urllib.urlencode({ 'sku': sku, 'ref': magnatune_partner_id })
196
193
if url not in urls:
197
194
Gtk.show_uri(screen, url, Gdk.CURRENT_TIME)
200
197
def download_album(self):
201
if selt.__settings['account_type'] != 'download':
202
# The user doesn't have a download account, so redirect them to the purchase page.
203
self.purchase_redirect()
198
if self.__settings['account-type'] != 'download':
199
# The user doesn't have a download account, so redirect them to the download signup page
200
self.download_redirect()
208
205
library = Gio.Settings("org.gnome.rhythmbox.rhythmdb")
209
206
library_location = library['locations'][0]
210
207
except IndexError, e:
211
RB.error_dialog(title = _("Couldn't purchase album"),
212
message = _("You must have a library location set to purchase an album."))
208
RB.error_dialog(title = _("Couldn't download album"),
209
message = _("You must have a library location set to download an album."))
215
212
tracks = self.get_entry_view().get_selected_entries()
218
215
for track in tracks:
219
sku = self.__sku_dict[self.__db.entry_get_string(track, RB.RhythmDBPropType.LOCATION)]
216
sku = self.__sku_dict[track.get_string(RB.RhythmDBPropType.LOCATION)]
229
226
def __update_catalogue(self):
230
def update_cb(result):
227
def update_cb(remote_changes):
231
228
self.__catalogue_check = None
230
f = open(magnatune_changes, 'r')
231
local_changes = f.read().strip()
235
remote_changes = remote_changes.strip()
236
print "local checksum %s, remote checksum %s" % (local_changes, remote_changes)
237
if local_changes != remote_changes:
239
f = open(magnatune_changes, 'w')
240
f.write(remote_changes + "\n")
243
print "unable to write local change id: %s" % str(e)
233
245
download_catalogue()
234
246
elif self.__has_loaded is False:
241
253
return info.filename;
244
def download_progress(complete, total):
256
def download_progress(copy, complete, total, self):
245
257
self.__load_progress = (complete, total)
246
258
self.__notify_status_changed()
248
def download_finished(uri, result):
250
success = uri.copy_finish(result)
260
def download_finished(copy, success, self):
262
print "catalog download failed"
263
print copy.get_error()
266
print "catalog download successful"
257
267
# done downloading, unzip to real location
258
268
catalog_zip = zipfile.ZipFile(magnatune_song_info_temp)
259
269
catalog = open(magnatune_song_info, 'w')
277
288
self.__updating = True
279
dest = Gio.file_new_for_path(magnatune_song_info_temp)
280
self.__catalogue_loader = Gio.Cancellable()
282
# For some reason, Gio.FileCopyFlags.OVERWRITE doesn't work for copy_async
291
df = Gio.file_new_for_path(magnatune_song_info_temp)
286
magnatune_song_info_uri.copy_async(dest,
288
progress_callback=download_progress,
289
flags=Gio.FileCopyFlags.OVERWRITE,
290
cancellable=self.__catalogue_loader)
295
self.__catalog_loader = RB.AsyncCopy()
296
self.__catalog_loader.set_progress(download_progress, self)
297
self.__catalog_loader.start(magnatune_song_info_uri, magnatune_song_info_temp, download_finished, self)
292
299
def load_catalogue():
293
def got_items(result, items):
294
account_type = self.__settings['account_type']
297
if account_type == 'none':
299
elif result is not None or len(items) == 0:
300
RB.error_dialog(title = _("Couldn't get account details"),
301
message = str(result))
305
username, password = items[0].secret.split('\n')
306
except ValueError: # Couldn't parse secret, possibly because it's empty
308
parser = xml.sax.make_parser()
309
parser.setContentHandler(TrackListHandler(self.__db, self.__entry_type, self.__sku_dict, self.__home_dict, self.__art_dict, account_type, username, password))
311
self.__catalogue_loader = rb.ChunkLoader()
312
self.__catalogue_loader.get_url_chunks(magnatune_song_info, 64*1024, True, catalogue_chunk_cb, parser)
314
def catalogue_chunk_cb(result, total, parser):
315
if not result or isinstance(result, Exception):
301
def catalogue_chunk_cb(loader, data, total, parser):
303
error = loader.get_error()
317
305
# report error somehow?
318
print "error loading catalogue: %s" % result
306
print "error loading catalogue: %s" % error
330
318
# restart in-progress downloads
331
319
# (doesn't really belong here)
332
for f in magnatune_in_progress_dir.enumerate_children('standard::name'):
320
for f in magnatune_in_progress_dir.enumerate_children('standard::name', Gio.FileQueryInfoFlags.NONE, None):
333
321
name = f.get_name()
334
322
if not name.startswith("in_progress_"):
336
uri = magnatune_in_progress_dir.resolve_relative_path(name).load_contents()[0]
324
(result, uri, etag) = magnatune_in_progress_dir.resolve_relative_path(name).load_contents(None)
337
325
print "restarting download from %s" % uri
338
self.__download_album(Gio.file_new_for_uri(uri), name[12:])
326
self.__download_album(uri, name[12:])
340
328
# hack around some weird chars that show up in the catalogue for some reason
341
result = result.replace("\x19", "'")
342
result = result.replace("\x13", "-")
330
data = data.replace("\x19", "'")
331
data = data.replace("\x13", "-")
345
result = result.replace("Rock & Roll", "Rock & Roll")
334
data = data.replace("Rock & Roll", "Rock & Roll")
349
338
except xml.sax.SAXParseException, e:
350
339
print "error parsing catalogue: %s" % e
352
load_size['size'] += len(result)
341
load_size['size'] += len(data)
353
342
self.__load_progress = (load_size['size'], total)
355
344
self.__notify_status_changed()
361
350
self.__notify_status_changed()
363
352
load_size = {'size': 0}
364
keyring.find_items(keyring.ITEM_GENERIC_SECRET, {'rhythmbox-plugin': 'magnatune'}, got_items)
367
self.__catalogue_check = rb.UpdateCheck()
368
self.__catalogue_check.check_for_update(magnatune_song_info, magnatune_song_info_uri.get_uri(), update_cb)
354
parser = xml.sax.make_parser()
355
parser.setContentHandler(TrackListHandler(self.__db, self.__entry_type, self.__sku_dict, self.__home_dict, self.__art_dict))
357
self.__catalogue_loader = RB.ChunkLoader()
358
self.__catalogue_loader.set_callback(catalogue_chunk_cb, parser)
359
self.__catalogue_loader.start(magnatune_song_info, 64*1024)
362
self.__catalogue_check = rb.Loader()
363
self.__catalogue_check.get_url(magnatune_changed_uri, update_cb)
371
366
def __show_loading_screen(self, show):
397
392
def __auth_download(self, sku): # http://magnatune.com/info/api
398
def got_items(result, items):
399
if result is not None or len(items) == 0:
400
RB.error_dialog(title = _("Couldn't get account details"),
401
message = str(result))
405
username, password = items[0].secret.split('\n')
406
except ValueError: # Couldn't parse secret, possibly because it's empty
409
print "downloading album: " + sku
411
'id': magnatune_partner_id,
414
url = magnatune_api_download_uri % (username, password)
415
url = url + urllib.urlencode(url_dict)
418
l.get_url(url, auth_data_cb, (username, password))
420
394
def auth_data_cb(data, (username, password)):
421
buy_album_handler = BuyAlbumHandler(self.__settings['format'])
395
dl_album_handler = DownloadAlbumHandler(self.__settings['format'])
422
396
auth_parser = xml.sax.make_parser()
423
auth_parser.setContentHandler(buy_album_handler)
397
auth_parser.setContentHandler(dl_album_handler)
430
404
data = data.replace("<br>", "") # get rid of any stray <br> tags that will mess up the parser
405
data = data.replace(" & ", " & ") # clean up some missing escaping
432
407
auth_parser.feed(data)
433
408
auth_parser.close()
435
410
# process the URI: add authentication info, quote the filename component for some reason
436
parsed = urlparse.urlparse(buy_album_handler.url)
411
parsed = urlparse.urlparse(dl_album_handler.url)
437
412
netloc = "%s:%s@%s" % (username, password, parsed.hostname)
439
414
spath = os.path.split(urllib.url2pathname(parsed.path))
443
418
authed = (parsed[0], netloc, path) + parsed[3:]
444
419
audio_dl_uri = urlparse.urlunparse(authed)
446
self.__download_album(Gio.file_new_for_uri(audio_dl_uri), sku)
421
print "download uri for %s is %s" % (sku, audio_dl_uri)
422
self.__download_album(audio_dl_uri, sku)
448
except MagnatunePurchaseError, e:
424
except MagnatuneDownloadError, e:
449
425
RB.error_dialog(title = _("Download Error"),
450
426
message = _("An error occurred while trying to authorize the download.\nThe Magnatune server returned:\n%s") % str(e))
451
427
except Exception, e:
428
sys.excepthook(*sys.exc_info())
452
429
RB.error_dialog(title = _("Error"),
453
430
message = _("An error occurred while trying to download the album.\nThe error text is:\n%s") % str(e))
456
keyring.find_items(keyring.ITEM_GENERIC_SECRET, {'rhythmbox-plugin': 'magnatune'}, got_items)
432
print "downloading album: " + sku
433
account = MagnatuneAccount.instance()
434
(account_type, username, password) = account.get()
436
'id': magnatune_partner_id,
439
url = magnatune_api_download_uri % (username, password)
440
url = url + urllib.urlencode(url_dict)
443
l.get_url(url, auth_data_cb, (username, password))
458
446
def __download_album(self, audio_dl_uri, sku):
459
def download_progress(current, total):
460
self.__downloads[str_uri] = (current, total)
447
def download_progress(copy, complete, total, self):
448
self.__downloads[audio_dl_uri] = (complete, total)
461
449
self.__notify_status_changed()
463
def download_finished(uri, result):
464
del self.__cancellables[str_uri]
465
del self.__downloads[str_uri]
468
success = uri.copy_finish(result)
471
print "Download not completed: " + str(e)
451
def download_finished(copy, success, self):
452
del self.__downloads[audio_dl_uri]
453
del self.__copies[audio_dl_uri]
455
print "download of %s finished: %s" % (audio_dl_uri, success)
474
457
threading.Thread(target=unzip_album).start()
480
463
manager = shell.props.ui_manager
481
464
manager.get_action("/MagnatuneSourceViewPopup/MagnatuneCancelDownload").set_sensitive(False)
483
shell.notify_custom(4000, _("Finished Downloading"), _("All Magnatune downloads have been completed."))
466
shell.notify_custom(4000, _("Finished Downloading"), _("All Magnatune downloads have been completed."), None, False)
485
468
self.__notify_status_changed()
489
472
library = Gio.Settings("org.gnome.rhythmbox.rhythmdb")
490
473
library_location = Gio.file_new_for_uri(library['locations'][0])
475
print "unzipping %s" % dest.get_path()
492
476
album = zipfile.ZipFile(dest.get_path())
493
477
for track in album.namelist():
494
478
track_uri = library_location.resolve_relative_path(track).get_uri()
479
print "zip file entry: %s => %s" % (track, track_uri)
496
481
track_uri = RB.sanitize_uri_for_filesystem(track_uri)
497
482
RB.uri_create_parent_dirs(track_uri)
499
track_out = Gio.file_new_for_uri(track_uri).create()
484
track_out = Gio.file_new_for_uri(track_uri).create(Gio.FileCreateFlags.NONE, None)
500
485
if track_out is not None:
501
track_out.write(album.read(track))
486
track_out.write(album.read(track), None)
487
track_out.close(None)
488
print "adding %s to library" % track_uri
503
489
self.__db.add_uri(track_uri)
506
492
remove_download_files()
508
494
def remove_download_files():
495
print "removing download files"
496
in_progress.delete(None)
513
499
in_progress = magnatune_in_progress_dir.resolve_relative_path("in_progress_" + sku)
514
500
dest = magnatune_in_progress_dir.resolve_relative_path(sku)
516
str_uri = audio_dl_uri.get_uri()
517
in_progress.replace_contents(str_uri, None, False, flags=Gio.FileCreateFlags.PRIVATE|Gio.FileCreateFlags.REPLACE_DESTINATION)
502
in_progress.replace_contents(str(audio_dl_uri),
505
Gio.FileCreateFlags.PRIVATE|Gio.FileCreateFlags.REPLACE_DESTINATION,
519
508
shell = self.props.shell
520
509
manager = shell.props.ui_manager
521
510
manager.get_action("/MagnatuneSourceViewPopup/MagnatuneCancelDownload").set_sensitive(True)
523
self.__downloads[str_uri] = (0, 0) # (current, total)
525
cancel = Gio.Cancellable()
526
self.__cancellables[str_uri] = cancel
528
513
# For some reason, Gio.FileCopyFlags.OVERWRITE doesn't work for copy_async
533
# no way to resume downloads, sadly
534
audio_dl_uri.copy_async(dest,
536
progress_callback=download_progress,
537
flags=Gio.FileCopyFlags.OVERWRITE,
519
dl.set_progress(download_progress, self)
520
dl.start(audio_dl_uri, dest.get_uri(), download_finished, self)
521
self.__downloads[audio_dl_uri] = (0, 0) # (current, total)
522
self.__copies[audio_dl_uri] = dl
541
525
def cancel_downloads(self):
542
for cancel in self.__cancellables.values():
526
for download in self.__copies.values():
545
529
shell = self.props.shell
546
530
manager = shell.props.ui_manager