~ubuntu-branches/ubuntu/precise/rhythmbox/precise-security

« back to all changes in this revision

Viewing changes to .pc/03_magnatune_partner.patch/plugins/magnatune/MagnatuneSource.py

  • Committer: Package Import Robot
  • Author(s): Rodney Dawes
  • Date: 2012-03-09 07:24:47 UTC
  • mfrom: (1.1.64)
  • Revision ID: package-import@ubuntu.com-20120309072447-ltf9ea8d9vj5zmiy
Tags: 2.95.5-0ubuntu1
* New upstream release. (LP: #949424)
  - Add support for allowing plug-ins to specify that they should be
    enabled by default. (LP: #934235)
  - Magnatune plug-in is re-enabled upstream.
  - Removal of gtk_dialog_run in most all cases.
  - Porting of more plug-ins to python-gi. 
* debian/control, debian/*.install:
  - Remove the useless coherence plug-in install file.
  - Move some core plug-ins into main rhythmbox package.
    + audiocd, generic-player, iradio, mmkeys, power-manager, rb
  - Split out the NPAPI plug-in to a separate package.
  - Split out the zeitgeist plug-in to a separate package.
  - Split out the magnatune store plug-in to a separate package.
  - Split out the visualizer plug-in to a separate package.
    + Disable building the visualizer plug-in package, as it needs
      libmx which is in universe and not main.
* debian/patches/*:
  - Refreshed patches against new source that had problems applying.
  - Remvoe the mpris name patch which is included upstream.

Show diffs side-by-side

added added

removed removed

Lines of Context:
26
26
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
27
27
 
28
28
import os
 
29
import sys
29
30
import xml
30
31
import urllib
31
32
import urlparse
32
33
import threading
33
34
import zipfile
34
 
import gnomekeyring as keyring
35
35
 
36
36
import rb
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
40
39
 
41
40
from TrackListHandler import TrackListHandler
42
 
from BuyAlbumHandler import BuyAlbumHandler, MagnatunePurchaseError
 
41
from DownloadAlbumHandler import DownloadAlbumHandler, MagnatuneDownloadError
 
42
import MagnatuneAccount
43
43
 
44
44
import gettext
45
45
gettext.install('rhythmbox', RB.locale_dir())
47
47
magnatune_partner_id = "rhythmbox"
48
48
 
49
49
# URIs
50
 
magnatune_song_info_uri = Gio.file_new_for_uri("http://magnatune.com/info/song_info_xml.zip")
 
50
magnatune_song_info_uri = "http://magnatune.com/info/song_info_xml.zip"
 
51
magnatune_changed_uri = "http://magnatune.com/info/changed.txt"
51
52
magnatune_buy_album_uri = "https://magnatune.com/buy/choose?"
52
53
magnatune_api_download_uri = "http://%s:%s@download.magnatune.com/buy/membership_free_dl_xml?"
53
54
 
56
57
 
57
58
magnatune_song_info = os.path.join(magnatune_cache_dir.get_path(), 'song_info.xml')
58
59
magnatune_song_info_temp = os.path.join(magnatune_cache_dir.get_path(), 'song_info.zip.tmp')
 
60
magnatune_changes = os.path.join(magnatune_cache_dir.get_path(), 'changed.txt')
59
61
 
60
62
 
61
63
class MagnatuneSource(RB.BrowserSource):
85
87
 
86
88
                # album download stuff
87
89
                self.__downloads = {} # keeps track of download progress for each file
88
 
                self.__cancellables = {} # keeps track of Gio.Cancellable objects so we can abort album downloads
 
90
                self.__copies = {} # keeps copy objects for each file
89
91
 
90
 
                self.__art_store = RB.ExtDB("album-art")
 
92
                self.__art_store = RB.ExtDB(name="album-art")
91
93
 
92
94
        #
93
95
        # RBSource methods
115
117
                        qm = self.props.query_model
116
118
                        return (qm.compute_status_normal("%d song", "%d songs"), None, 2.0)
117
119
 
118
 
        def do_get_ui_actions(self):
119
 
                return ["MagnatuneDownloadAlbum",
120
 
                        "MagnatuneArtistInfo",
121
 
                        "MagnatuneCancelDownload"]
122
 
 
123
120
        def do_selected(self):
124
121
                if not self.__activated:
125
122
                        shell = self.props.shell
144
141
        def do_impl_can_delete(self):
145
142
                return False
146
143
 
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)
151
148
 
152
149
 
153
150
        def do_delete_thyself(self):
179
176
                urls = set([])
180
177
 
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)
186
183
                                urls.add(url)
187
184
 
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()
191
188
                urls = set([])
192
189
 
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)
198
195
                                urls.add(url)
199
196
 
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()
204
201
                        return
205
202
 
206
203
                try:
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."))
213
210
                        return
214
211
 
215
212
                tracks = self.get_entry_view().get_selected_entries()
216
213
                skus = []
217
214
 
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)]
220
217
                        if sku in skus:
221
218
                                continue
222
219
                        skus.append(sku)
227
224
        #
228
225
 
229
226
        def __update_catalogue(self):
230
 
                def update_cb(result):
 
227
                def update_cb(remote_changes):
231
228
                        self.__catalogue_check = None
232
 
                        if result is True:
 
229
                        try:
 
230
                                f = open(magnatune_changes, 'r')
 
231
                                local_changes = f.read().strip()
 
232
                        except:
 
233
                                local_changes = ""
 
234
 
 
235
                        remote_changes = remote_changes.strip()
 
236
                        print "local checksum %s, remote checksum %s" % (local_changes, remote_changes)
 
237
                        if local_changes != remote_changes:
 
238
                                try:
 
239
                                        f = open(magnatune_changes, 'w')
 
240
                                        f.write(remote_changes + "\n")
 
241
                                        f.close()
 
242
                                except Exception, e:
 
243
                                        print "unable to write local change id: %s" % str(e)
 
244
 
233
245
                                download_catalogue()
234
246
                        elif self.__has_loaded is False:
235
247
                                load_catalogue()
241
253
                                                return info.filename;
242
254
                                return None
243
255
 
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()
247
259
 
248
 
                        def download_finished(uri, result):
249
 
                                try:
250
 
                                        success = uri.copy_finish(result)
251
 
                                except:
252
 
                                        success = False
253
 
 
 
260
                        def download_finished(copy, success, self):
254
261
                                if not success:
 
262
                                        print "catalog download failed"
 
263
                                        print copy.get_error()
255
264
                                        return
256
265
 
 
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')
266
276
                                catalog.close()
267
277
                                catalog_zip.close()
268
278
 
269
 
                                dest.delete()
 
279
                                df = Gio.file_new_for_path(magnatune_song_info_temp)
 
280
                                df.delete(None)
270
281
                                self.__updating = False
271
282
                                self.__catalogue_loader = None
272
283
                                self.__notify_status_changed()
276
287
 
277
288
                        self.__updating = True
278
289
 
279
 
                        dest = Gio.file_new_for_path(magnatune_song_info_temp)
280
 
                        self.__catalogue_loader = Gio.Cancellable()
281
290
                        try:
282
 
                                # For some reason, Gio.FileCopyFlags.OVERWRITE doesn't work for copy_async
283
 
                                dest.delete()
 
291
                                df = Gio.file_new_for_path(magnatune_song_info_temp)
 
292
                                df.delete(None)
284
293
                        except:
285
294
                                pass
286
 
                        magnatune_song_info_uri.copy_async(dest,
287
 
                                                           download_finished,
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)
291
298
 
292
299
                def load_catalogue():
293
 
                        def got_items(result, items):
294
 
                                account_type = self.__settings['account_type']
295
 
                                username = ""
296
 
                                password = ""
297
 
                                if account_type == 'none':
298
 
                                        pass
299
 
                                elif result is not None or len(items) == 0:
300
 
                                        RB.error_dialog(title = _("Couldn't get account details"),
301
 
                                                        message = str(result))
302
 
                                        return
303
 
                                else:
304
 
                                        try:
305
 
                                                username, password = items[0].secret.split('\n')
306
 
                                        except ValueError: # Couldn't parse secret, possibly because it's empty
307
 
                                                pass
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))
310
 
 
311
 
                                self.__catalogue_loader = rb.ChunkLoader()
312
 
                                self.__catalogue_loader.get_url_chunks(magnatune_song_info, 64*1024, True, catalogue_chunk_cb, parser)
313
 
 
314
 
                        def catalogue_chunk_cb(result, total, parser):
315
 
                                if not result or isinstance(result, Exception):
316
 
                                        if result:
 
300
 
 
301
                        def catalogue_chunk_cb(loader, data, total, parser):
 
302
                                if data is None:
 
303
                                        error = loader.get_error()
 
304
                                        if error:
317
305
                                                # report error somehow?
318
 
                                                print "error loading catalogue: %s" % result
 
306
                                                print "error loading catalogue: %s" % error
319
307
 
320
308
                                        try:
321
309
                                                parser.close()
329
317
 
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_"):
335
323
                                                        continue
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:])
339
327
                                else:
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", "-")
 
329
                                        data = str(data.str)
 
330
                                        data = data.replace("\x19", "'")
 
331
                                        data = data.replace("\x13", "-")
343
332
 
344
333
                                        # argh.
345
 
                                        result = result.replace("Rock & Roll", "Rock & Roll")
 
334
                                        data = data.replace("Rock & Roll", "Rock & Roll")
346
335
 
347
336
                                        try:
348
 
                                                parser.feed(result)
 
337
                                                parser.feed(data)
349
338
                                        except xml.sax.SAXParseException, e:
350
339
                                                print "error parsing catalogue: %s" % e
351
340
 
352
 
                                        load_size['size'] += len(result)
 
341
                                        load_size['size'] += len(data)
353
342
                                        self.__load_progress = (load_size['size'], total)
354
343
 
355
344
                                self.__notify_status_changed()
361
350
                        self.__notify_status_changed()
362
351
 
363
352
                        load_size = {'size': 0}
364
 
                        keyring.find_items(keyring.ITEM_GENERIC_SECRET, {'rhythmbox-plugin': 'magnatune'}, got_items)
365
 
 
366
 
 
367
 
                self.__catalogue_check = rb.UpdateCheck()
368
 
                self.__catalogue_check.check_for_update(magnatune_song_info, magnatune_song_info_uri.get_uri(), update_cb)
 
353
 
 
354
                        parser = xml.sax.make_parser()
 
355
                        parser.setContentHandler(TrackListHandler(self.__db, self.__entry_type, self.__sku_dict, self.__home_dict, self.__art_dict))
 
356
 
 
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)
 
360
 
 
361
 
 
362
                self.__catalogue_check = rb.Loader()
 
363
                self.__catalogue_check.get_url(magnatune_changed_uri, update_cb)
369
364
 
370
365
 
371
366
        def __show_loading_screen(self, show):
395
390
        #
396
391
 
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))
402
 
                                return
403
 
 
404
 
                        try:
405
 
                                username, password = items[0].secret.split('\n')
406
 
                        except ValueError: # Couldn't parse secret, possibly because it's empty
407
 
                                username = ""
408
 
                                password = ""
409
 
                        print "downloading album: " + sku
410
 
                        url_dict = {
411
 
                                'id':   magnatune_partner_id,
412
 
                                'sku':  sku
413
 
                        }
414
 
                        url = magnatune_api_download_uri % (username, password)
415
 
                        url = url + urllib.urlencode(url_dict)
416
 
 
417
 
                        l = rb.Loader()
418
 
                        l.get_url(url, auth_data_cb, (username, password))
419
393
 
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)
424
398
 
425
399
                        if data is None:
426
400
                                # hmm.
428
402
 
429
403
                        try:
430
404
                                data = data.replace("<br>", "") # get rid of any stray <br> tags that will mess up the parser
 
405
                                data = data.replace(" & ", " &amp; ") # clean up some missing escaping
431
406
                                # print data
432
407
                                auth_parser.feed(data)
433
408
                                auth_parser.close()
434
409
 
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)
438
413
 
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)
445
420
 
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)
447
423
 
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))
454
431
 
455
 
 
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()
 
435
                url_dict = {
 
436
                        'id':   magnatune_partner_id,
 
437
                        'sku':  sku
 
438
                }
 
439
                url = magnatune_api_download_uri % (username, password)
 
440
                url = url + urllib.urlencode(url_dict)
 
441
 
 
442
                l = rb.Loader()
 
443
                l.get_url(url, auth_data_cb, (username, password))
 
444
 
457
445
 
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()
462
450
 
463
 
                def download_finished(uri, result):
464
 
                        del self.__cancellables[str_uri]
465
 
                        del self.__downloads[str_uri]
466
 
 
467
 
                        try:
468
 
                                success = uri.copy_finish(result)
469
 
                        except Exception, e:
470
 
                                success = False
471
 
                                print "Download not completed: " + str(e)
472
 
 
 
451
                def download_finished(copy, success, self):
 
452
                        del self.__downloads[audio_dl_uri]
 
453
                        del self.__copies[audio_dl_uri]
 
454
 
 
455
                        print "download of %s finished: %s" % (audio_dl_uri, success)
473
456
                        if success:
474
457
                                threading.Thread(target=unzip_album).start()
475
458
                        else:
480
463
                                manager = shell.props.ui_manager
481
464
                                manager.get_action("/MagnatuneSourceViewPopup/MagnatuneCancelDownload").set_sensitive(False)
482
465
                                if success:
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)
484
467
 
485
468
                        self.__notify_status_changed()
486
469
 
489
472
                        library = Gio.Settings("org.gnome.rhythmbox.rhythmdb")
490
473
                        library_location = Gio.file_new_for_uri(library['locations'][0])
491
474
 
 
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)
495
480
 
496
481
                                track_uri = RB.sanitize_uri_for_filesystem(track_uri)
497
482
                                RB.uri_create_parent_dirs(track_uri)
498
483
 
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))
502
 
                                        track_out.close()
 
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)
504
490
 
505
491
                        album.close()
506
492
                        remove_download_files()
507
493
 
508
494
                def remove_download_files():
509
 
                        in_progress.delete()
510
 
                        dest.delete()
511
 
 
 
495
                        print "removing download files"
 
496
                        in_progress.delete(None)
 
497
                        dest.delete(None)
512
498
 
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)
515
501
 
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),
 
503
                                             None,
 
504
                                             False,
 
505
                                             Gio.FileCreateFlags.PRIVATE|Gio.FileCreateFlags.REPLACE_DESTINATION,
 
506
                                             None)
518
507
 
519
508
                shell = self.props.shell
520
509
                manager = shell.props.ui_manager
521
510
                manager.get_action("/MagnatuneSourceViewPopup/MagnatuneCancelDownload").set_sensitive(True)
522
511
 
523
 
                self.__downloads[str_uri] = (0, 0) # (current, total)
524
 
 
525
 
                cancel = Gio.Cancellable()
526
 
                self.__cancellables[str_uri] = cancel
527
512
                try:
528
513
                        # For some reason, Gio.FileCopyFlags.OVERWRITE doesn't work for copy_async
529
 
                        dest.delete()
 
514
                        dest.delete(None)
530
515
                except:
531
516
                        pass
532
517
 
533
 
                # no way to resume downloads, sadly
534
 
                audio_dl_uri.copy_async(dest,
535
 
                                        download_finished,
536
 
                                        progress_callback=download_progress,
537
 
                                        flags=Gio.FileCopyFlags.OVERWRITE,
538
 
                                        cancellable=cancel)
 
518
                dl = RB.AsyncCopy()
 
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
539
523
 
540
524
 
541
525
        def cancel_downloads(self):
542
 
                for cancel in self.__cancellables.values():
543
 
                        cancel.cancel()
 
526
                for download in self.__copies.values():
 
527
                        download.cancel()
544
528
 
545
529
                shell = self.props.shell
546
530
                manager = shell.props.ui_manager