Commits

Damián Nohales committed 1ac5afc

Many fixes on DownloadsList and download behavior

- Fixed race condition when a download is removed from the list.
- DownloadList emit the signal "downloads-changed" when the number of downloads count has been changed.

  • Participants
  • Parent commits e3ff9b6

Comments (0)

Files changed (4)

File lib/AbstractSongList.py

         """
         Returns the model row related to the song passed by parameter.
         """
-        return self.get_model()[self.get_song_iter(song)]
+        iter = self.get_song_iter(song)
+        if iter == None:
+            return None
+        else:
+            return self.get_model()[iter]
         
     def get_song_path(self, song):
         """
         return range(len(self.get_model()))
     
     def get_selected_rows(self):
-        self.get_view().get_selection().get_selected_rows()[1]
+        return self.get_view().get_selection().get_selected_rows()[1]
         

File lib/DownloadList.py

 from enviroment import config
 from AbstractSongList import AbstractSongList
+from Song import Song
+import threading
+import gobject
 import gtk
 import os
     
     """
     
     def __init__(self, view):
+        self.__gobject_init__()
         AbstractSongList.__init__(self, view)
+        self._downcount_semaphore = threading.Semaphore()
+        self._remove_semaphore = threading.Semaphore()
+        self.downloads_count = 0
     
     def create_model(self):
         # Song, File Name, SongID, Progress, Size, Icon info stock
     
     def __append_song_to_model(self, song):
         # Change song local ID to fix the search by song
-        song = song.clone()
+        # song = Song(song.get_data())
+        # The above line cause problems, I don't know why :( .
         
         self.get_model().append([
             song,
         Append song for download, if file exists, tries to continue the download
         """
         self.__append_song_to_model(song);
-        song.download_file(False)
+        song.start_download(False)
         
     def append_song_restarting(self, song):
         """
         Append song for download, forcing to redownload the file
         """
         self.__append_song_to_model(song);
-        song.download_file(True)
+        song.start_download(True)
         
     def create_view(self):
         column = gtk.TreeViewColumn("", gtk.CellRendererPixbuf(), stock_id = 5)
         self.get_view().append_column(column)
     
     def on_download_initializing(self, song):
+        print "[Download initializing]", song.get_id()
         self.get_song_row(song)[5] = "gtk-network"
+        
+        self.emit_download_changed()
     
     def on_download_started(self, song):
         print "[Download started]", song.get_id()
     
     def on_download_updated(self, song):
+        self._remove_semaphore.acquire();
         row = self.get_song_row(song)
+        if row == None:
+            return
         
         row[3] = song.get_download_progress()
         row[4] = "%.02f MB" % (song.get_file_size() / (1024 ** 2))
         row[5] = "gtk-go-down"
+        self._remove_semaphore.release();
     
     def on_download_completed(self, song):
         print "[Download completed]", song.get_id()
         row[3] = 100
         row[5] = "gtk-ok"
         
+        self.emit_download_changed()
+        
     def on_download_paused(self, song):
         print "[Download paused]", song.get_id()
         row = self.get_song_row(song)
         row[3] = 0
         row[5] = "gtk-media-stop"
         
+        self.emit_download_changed()
+        
     def on_download_canceled(self, song):
+        self._remove_semaphore.acquire()
         print "[Download canceled]", song.get_id()
-        self.get_model().remove(self.get_song_iter(song))
+        try:
+            self.get_model().remove(self.get_song_iter(song))
+        except:
+            pass
+        self._remove_semaphore.release()
+        
+        self.emit_download_changed()
     
     def on_download_error(self, song, msg):
         print "[Download error]", song.get_id(), ":", msg
         self.get_song_row(song)[5] = "gtk-dialog-error"
         
+        self.emit_download_changed()
+        
+    def emit_download_changed(self):
+        self._downcount_semaphore.acquire()
+        
+        self.emit("downloads-changed")
+        
+        self._downcount_semaphore.release()
+        
     def stop_all_downloads(self):
         """
         Stop all downloads in a sync way for quit the main app
         """
         for i in self.range():
             self.get_song(i).pause_download_sync()
-        
+
+gobject.type_register(DownloadList)
+# Emitted when the cover download was finished
+gobject.signal_new("downloads-changed", DownloadList, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())

File lib/SharkDown.py

 
         # Download list definition
         self.downloads = DownloadList(builder.get_object('list_downloads'))
+        self.downloads.connect("downloads-changed", self.on_downloads_changed)
         self.downloads.get_view().hide_all()
         
         # Downloads related initialization
         self.downloads_count = builder.get_object('downloads_count')
         self.downloads_expander = builder.get_object('expander_download')
-        self.update_downloads_count()
         self.downmenu = builder.get_object('downloadmenu')
+        self.on_downloads_changed(self.downloads)
         
         # Song lists initialization
         if os.path.exists("%s/.gsharkdown/playlist.pkl" % os.environ.get("HOME")):
         
     def on_init_thread_end(self):
         self.window.set_sensitive(True)
-        self.entry.set_text("metallica")
-        self.on_search_grooveshark(self.entry)
         if os.path.exists("%s/.gsharkdown/downqueue.pkl" % os.environ.get("HOME")):
             self.load_downqueue_list("%s/.gsharkdown/downqueue.pkl" % os.environ.get("HOME"))
 
         Copy the selected song name from the results list in the
         GNOME Clipboard
         """
-        select = self.result_view.get_selection().get_selected_rows()
-        if self.result_view.get_selection().count_selected_rows() > 0:
-            song = self.result_song(select[1][0])
-            copystring = "%s - %s" % (song['ArtistName'], song['SongName'])
+        select = self.result.get_selected_rows()
+        if len(select) > 0:
+            song = self.result.get_song(select[0])
+            copystring = "%s - %s" % (song.get_artist(), song.get_title())
             clipboard = gtk.clipboard_get()
             clipboard.set_text(copystring)
             clipboard.store()
         """
         Starts the download thread
         """
-        select = self.result.get_view().get_selection().get_selected_rows()
-        for path in select[1]:
-            song = self.result_song(path[0])
+        select = self.result.get_selected_rows()
+        for path in select:
+            song = self.result.get_song(path)
             
             filename = self.get_overwritten_filename(song.get_default_filename())
 
                 song.set_filename(filename)
                 self.downloads.append_song_restarting(song)
 
-    def update_downloads_count(self):
+    def on_downloads_changed(self, widget, data = None):
         """
         Update the label wich indicates the downloads count.
         """
-        count = self.downloads.get_model().iter_n_children(None)
+        count = 0
+        iter = self.downloads.first()
+        while iter != None:
+            if self.downloads.get_song(iter).state == Song.STATE_DOWNLOADING:
+                count += 1
+            iter = self.downloads.next(iter)
         text = gettext.ngettext("Downloading %d file", "Downloading %d files", count) % count
         self.downloads_count.set_label(text)
 
             else:
                 self.downmenu.popup(None, None, None, event.button, event.time)
 
-    def on_cancel_all_downloads(self):
+    def on_cancel_all_downloads(self, widget, data = None):
         """
         Cancel all downloads
         """
         iter = self.downloads.first()
         while iter != None:
-            self.downloads.get_song(iter).cancel_download()
+            cur = iter
             iter = self.downloads.next(iter)
+            self.downloads.get_song(cur).cancel_download()
             
-    def on_pause_all_downloads(self):
+    def on_stop_all_downloads(self, widget, data = None):
         """
         Cancel all downloads
         """
             self.downloads.get_song(iter).pause_download()
             iter = self.downloads.next(iter)
             
-    def on_resume_all_downloads(self):
+    def on_resume_all_downloads(self, widget, data = None):
         """
         Cancel all downloads
         """
         iter = self.downloads.first()
         while iter != None:
-            if self.downloads.get_song(iter).is_downloading() == False:
-                self.downloads.get_song(iter).download_file(False)
+            self.downloads.get_song(iter).resume_download()
             iter = self.downloads.next(iter)
 
     def on_cancel_download(self, menu, data = None):
         """
         Cancel the download. Have to check how to cancel the thread.
         """
-        select = self.downloads_view.get_selection().get_selected_rows()
-        for path in select[1]:
-            self.cancel_download(path, 'cancel')
+        select = self.downloads.get_selected_rows()
+        for path in select:
+            song = self.downloads.get_song(path).cancel_download()
 
     def on_stop_download(self, button, data = None):
         """
         list will be saved.
         Stop download, in order to be resumed later
         """
-        select = self.downloads_view.get_selection().get_selected_rows()
-        for path in select[1]:
-            self.cancel_download(path, 'stop')
+        select = self.downloads.get_selected_rows()
+        for path in select:
+            self.downloads.get_song(path).pause_download()
 
     def on_resume_download(self, button, data = None):
         """
         load the download list in order to resume.
         Resume stopped download
         """
-        select = self.downloads_view.get_selection().get_selected_rows()
-        for path in select[1]:
-            for i in range(0, len(self.downqueue)):
-                if path == self.downloads.get_path(self.downqueue[i]['iter']):
-                    tDownload = DownloadThread(self,
-                                               self.downqueue[i]['song'],
-                                               self.downqueue[i]['filename'],
-                                               0, i)
-                    tDownload.start()
-
-        #tDownload = DownloadThread(self, self.downqueue[], filename)
-        #tDownload.start()
+        select = self.downloads.get_selected_rows()
+        for path in select:
+            self.downloads.get_song(path).resume_download()
 
     def on_love_song(self, button):
         if self.current_song == None:
         u'ArtistName': u'Viejas Locas'
     }
     """
+    STATE_NOT_STARTED = 0
+    STATE_CANCELED = 1
+    STATE_PAUSED = 2
+    STATE_DOWNLOADING = 3
+    STATE_COMPLETED = 4
+    STATE_ERROR = 5
     last_song_local_id = 0
     
     def __init__(self, data):
         self.download_progress = None
         self.file_size = None
         self.filename = None
-        self.download_thread = DownloadThread(self)
+        self.state = Song.STATE_NOT_STARTED
+        self.download_thread = None
         
         try:
             self.filename = self.data["filename"]
         
         if self.get_cover_filename() == "":
             self.set_cover_missed_pixbuf()
+            
+        self.connect("download-initializing", self.on_download_initializing)
+        self.connect("download-paused", self.on_download_paused)
+        self.connect("download-canceled", self.on_download_canceled)
+        self.connect("download-completed", self.on_download_completed)
+        self.connect("download-error", self.on_download_error)
+    
+    def on_download_initializing(self, song):
+        self.state = Song.STATE_DOWNLOADING
+        
+    def on_download_paused(self, song):
+        self.state = Song.STATE_PAUSED
+        
+    def on_download_canceled(self, song):
+        self.state = Song.STATE_CANCELED
+        
+    def on_download_completed(self, song):
+        self.state = Song.STATE_COMPLETED
+        
+    def on_download_error(self, song):
+        self.state = Song.STATE_ERROR
     
     def __getitem__(self, key):
         """
         thread = SongCoverThread(self)
         thread.start()
         
-    def download_file(self, restart = False, speed = None):
+    def start_download(self, restart = False, speed = None):
         """
         Starts the song download, the object will emit various signals to
         handle the download progress.
         if speed == None:
             speed = int(config()["speed_limit"])
         
-        self.restart = restart
-        self.speed = speed
+        self.download_thread = DownloadThread(self)
+        self.download_thread.restart = restart
+        self.download_thread.speed = speed
         self.download_thread.start()
         
     def is_downloading(self):
         """
         Returns true if the download is running
         """
-        return self.download_thread.is_alive()
+        return self.download_thread != None and self.download_thread.is_alive()
     
     def is_paused(self):
         """
         Returns true if the download was stopped by pausing it
         """
-        return self.is_downloading() == False and self.download_thread.is_canceled == False
+        return self.download_thread != None and self.is_downloading() == False and self.download_thread.is_canceled == False
         
     def pause_download(self):
         """
         """
         if self.is_downloading():
             self.download_thread.cancel()
+        elif self.is_paused():
+            os.remove(self.get_filename())
+            self.emit("download-canceled")
+            
+    def resume_download(self):
+        """
+        Resume the file download, the only difference with start_download is that
+        this method checks wether the file is downloading, ignore the action
+        in that case.
+        """
+        if self.is_downloading() == False:
+            self.start_download(False)
         
     def pause_download_sync(self):
         """
         """
         if self.is_downloading():
             self.download_thread.pause()
-            self.download_thread.join();
+            self.download_thread.join()
+            
+    def cancel_download_sync(self):
+        """
+        Cancel the current download and wait until it stops altogether.
+        """
+        if self.is_downloading():
+            self.download_thread.cancel()
+            self.download_thread.join()
             
     def get_download_progress(self):
         """
             key = groove.getStreamKeyFromSongIDEx(self.song.get_id())
         except Exception as e:
             self.song.emit("download-error", e.__str__())
-            raise
+            return
 
         try:
             url = "http://" + key["result"]["%s" % self.song.get_id()]["ip"] + "/stream.php"
             self.file.close()
             os.remove(self.song.get_filename())
             self.song.emit("download-error", e.__str__())
-            raise
+            return
             
         self.song.emit("download-stopped")
     
                 self.file.truncate(0)
     
     def hook(self, downloadTotal, downloadCurrent, uploadTotal, uploadCurrent):
+        if self.stopped():
+            return True
+        
         if downloadTotal > 0:
             if self.first_hook:
                 self.first_hook = False
 
             if progress <= 99:
                 self.song.emit("download-updated")
-
-        if self.stopped():
-            return True
-        else:
-            return False
+        
+        return False