Commits

Damián Nohales  committed 63b4fd9

Created the DownloadList class, implemented song download on Song class, remains refactoring

I guess this is a good moment to start the refactoring to use the new classes.
NOTE: There is a regression in the downloads, remains to implement pause/resume support again.

  • Participants
  • Parent commits c337e22
  • Branches refactoring

Comments (0)

Files changed (4)

File lib/DownloadList.py

+from AbstractSongList import AbstractSongList
+import gtk
+import os
+    
+class DownloadList(AbstractSongList):
+    def __init__(self, view):
+        AbstractSongList.__init__(self, view)
+    
+    def create_model(self):
+        # Song, File Name, SongID, Progress, Size
+        return gtk.ListStore(object, str, str, int, str)
+    
+    def append_song(self, song):
+        self.get_model().append([
+            song,
+            unicode(os.path.basename(song.get_filename()), errors = 'replace'),
+            song.get_id(),
+            0,
+            ""
+        ])
+        
+        song.connect("download-started", self.on_download_started)
+        song.connect("download-updated", self.on_download_updated)
+        song.connect("download-completed", self.on_download_completed)
+        song.connect("download-error", self.on_download_error)
+        
+        song.download_file()
+        
+    def create_view(self):
+        rendererText = gtk.CellRendererText()
+        column = gtk.TreeViewColumn(_("File name"), rendererText, text = 1)
+        column.set_resizable(True)
+        self.get_view().append_column(column)
+
+        rendererText = gtk.CellRendererText()
+        column = gtk.TreeViewColumn(_("SongID"), rendererText, text = 2)
+        column.set_resizable(True)
+        self.get_view().append_column(column)
+
+        rendererProgress = gtk.CellRendererProgress()
+        column = gtk.TreeViewColumn(_("Download progress"), rendererProgress, value = 3)
+        column.set_resizable(True)
+        self.get_view().append_column(column)
+
+        rendererText = gtk.CellRendererText()
+        column = gtk.TreeViewColumn(_("Size"), rendererText, text = 4)
+        column.set_resizable(True)
+        self.get_view().append_column(column)
+        
+    def on_download_started(self, song):
+        print "Started"
+    
+    def on_download_updated(self, song):
+        self.get_song_row(song)[3] = song.get_download_progress()
+        self.get_song_row(song)[4] = "%.02f MB" % (song.get_file_size() / (1024 ** 2))
+    
+    def on_download_completed(self, song):
+        self.get_model().remove(self.get_song_iter(song))
+    
+    def on_download_error(self, song, msg):
+        print "ERROR: ", msg

File lib/SharkDown.py

 import os
 import sys
 import random
-from tfuncs import DownloadThread
 from tfuncs import PlayThread
 from tfuncs import SearchThread
 from tfuncs import KeyListenerThread
 from tfuncs import UpdateThread
 from SearchResultList import SearchResultList
 from PlayList import PlayList
+from DownloadList import DownloadList
 from Song import Song
 import guihelpers
 import urllib2
         self.sw2.add(self.playlist.get_view())
 
         # Download list definition
-        # Model: File Name, SongID, Progress, Size, Thread Object
-        self.downloads = gtk.ListStore(str, str, int, str, object)
-        self.downloads_view = builder.get_object('list_downloads')
-        self.downloads_view.set_model(self.downloads)
-        self.downloads_view.hide_all()
-        self._create_downloads_columns()
+        self.downloads = DownloadList(builder.get_object('list_downloads'))
+        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.windowstate = 0
             return True
 
-    def _create_downloads_columns(self):
-        """
-        Generate the TreeView columns in the main window
-        for the download list items
-        """
-        rendererText = gtk.CellRendererText()
-        column = gtk.TreeViewColumn(_("File name"), rendererText, text = 0)
-        column.set_resizable(True)
-        self.downloads_view.append_column(column)
-
-        rendererText = gtk.CellRendererText()
-        column = gtk.TreeViewColumn(_("SongID"), rendererText, text = 1)
-        column.set_resizable(True)
-        self.downloads_view.append_column(column)
-
-        rendererProgress = gtk.CellRendererProgress()
-        column = gtk.TreeViewColumn(_("Download progress"), rendererProgress, value = 2)
-        column.set_resizable(True)
-        self.downloads_view.append_column(column)
-
-        rendererText = gtk.CellRendererText()
-        column = gtk.TreeViewColumn(_("Size"), rendererText, text = 3)
-        column.set_resizable(True)
-        self.downloads_view.append_column(column)
-
     def quit_app(self, widget, data = None):
         """
         Confirmation dialog when exiting the application
         """
         Starts the download thread
         """
-        select = self.result_view.get_selection().get_selected_rows()
+        select = self.result.get_view().get_selection().get_selected_rows()
         for path in select[1]:
             song = self.result_song(path[0])
-
-            #filename = "%s - %s.mp3" % (song["ArtistName"].strip("<>:\"/\|?*"),
-            #                        song["SongName"].strip("<>:\"/\|?*"))
-            pattern = config()['file_pattern'] + ".mp3"
-            filename = pattern.format(artist = song['ArtistName'].strip("<>:\"/\|?&*"),
-                                      song = song["SongName"].strip("<>:\"/\|?&*"),
-                                      album = song["AlbumName"].strip("<>:\"/\|?&*"))
-            filename = filename.replace('/', '-')
-            filename = os.path.join(config()['down_path'], filename)
-            filename = self.get_overwritten_filename(filename)
+            
+            filename = self.get_overwritten_filename(song.get_default_filename())
 
             if filename != None:
-                try:
-                    tDownload = DownloadThread(self, song, filename)
-                    tDownload.start()
-                except:
-                    pass
+                song.set_filename(filename)
+                self.downloads.append_song(song)
 
     def update_downloads_count(self):
         """
         Update the label wich indicates the downloads count.
         """
-        count = self.downloads.iter_n_children(None)
+        count = self.downloads.get_model().iter_n_children(None)
         text = gettext.ngettext("Downloading %d file", "Downloading %d files", count) % count
         self.downloads_count.set_label(text)
 
-from enviroment import env
+from enviroment import env, config
+import os
 import gtk
 import gobject
 import threading
 import urllib2
+import pycurl
+import time
+import groove
 
 class Song(gobject.GObject):
     """
         self.__gobject_init__()
         self.data = data
         self.cover_pixbuf = None
+        self.download_thread = None
+        self.download_progress = None
+        self.file_size = None
+        self.filename = self.get_default_filename()
         
         if self.get_cover_filename() == "":
             self.set_cover_missed_pixbuf()
     def download_cover(self):
         thread = SongCoverThread(self)
         thread.start()
+        
+    def download_file(self, options = None):
+        if options == None:
+            options = {"continue": None, "speed": 0}
+            
+        self.download_thread = DownloadThread(self, options)
+        self.download_thread.start()
+        
+    def cancel_download_sync(self):
+        self.download_thread.stop()
+        while(thread.is_alive()):
+            time.sleep(0.01)
+            
+    def get_download_progress(self):
+        return self.download_progress
+
+    def set_download_progress(self, value):
+        self.download_progress = value
+
+    def get_file_size(self):
+        return self.file_size
+
+    def set_file_size(self, value):
+        self.file_size = value
     
+    def get_filename(self):
+        return self.filename
+    
+    def set_filename(self, value):
+        self.filename = value
+        
+    def get_default_filename(self):
+        #filename = "%s - %s.mp3" % (song["ArtistName"].strip("<>:\"/\|?*"),
+        #                        song["SongName"].strip("<>:\"/\|?*"))
+        pattern = config()['file_pattern'] + ".mp3"
+        filename = pattern.format(artist = self.get_artist().strip("<>:\"/\|?&*"),
+                                  song = self.get_title().strip("<>:\"/\|?&*"),
+                                  album = self.get_album().strip("<>:\"/\|?&*"))
+        filename = filename.replace('/', '-')
+        filename = os.path.join(config()['down_path'], filename)
+        
+        return filename
 
 gobject.type_register(Song)
+# Emitted when the cover download was finished
 gobject.signal_new("cover-downloaded", Song, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
+# Emitted when download starts
+gobject.signal_new("download-started", Song, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
+# Emitted when cUrl receive data from GrooveShark (useful for update download progress)
+gobject.signal_new("download-updated", Song, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
+# Emitted when the download is successfully downloaded
+gobject.signal_new("download-completed", Song, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
+# Emitted when download is canceled
+gobject.signal_new("download-canceled", Song, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
+# Emitted when a download error occurs, an error message is passed by parameter
+gobject.signal_new("download-error", Song, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_STRING,))
+
 
 class SongCoverThread(threading.Thread):
     def __init__(self, song):
             print "Error while downloading cover: ", e
             self.song.set_cover_missed_pixbuf()
 
-        self.song.emit("cover-downloaded")
+        self.song.emit("cover-downloaded")
+        
+
+class DownloadThread(threading.Thread):
+
+    def __init__(self, song, options):
+        """
+        _window: The main window object
+        _song: The song to download
+        _filename: The full path file name to download the song
+        """
+        threading.Thread.__init__(self)
+
+        self.song = song
+        self.cont = options["continue"]
+        self.speed = options["speed"]
+        self.first_hook = True
+        self._stop = threading.Event()
+
+        try:
+            if self.cont == None:
+                self.file = open(self.song.get_filename(), "w")
+            else:
+                self.file = open(self.song.get_filename(), "ab")
+        except IOError:
+            self.song.emit("download-error", _("Failed to create '%s' for writing.") % self.song.get_filename())
+            return
+
+    def run(self):
+        try:
+            key = groove.getStreamKeyFromSongIDEx(self.song.get_id())
+        except Exception, e:
+            self.song.emit("download-error", e.args)
+            return
+
+        try:
+            url = "http://" + key["result"]["%s" % self.song.get_id()]["ip"] + "/stream.php"
+            url = str(url)
+            c = pycurl.Curl()
+            c.setopt(pycurl.URL, url)
+            c.setopt(pycurl.NOPROGRESS, 0)
+            c.setopt(pycurl.PROGRESSFUNCTION, self.hook)
+            c.setopt(pycurl.FAILONERROR, True)
+            if self.cont == None:
+                c.setopt(pycurl.FILE, self.file)
+            else:
+                c.setopt(pycurl.RESUME_FROM, os.path.getsize(self.song.get_filename()))
+                c.setopt(pycurl.WRITEDATA, self.file)
+            c.setopt(pycurl.POST, True)
+            c.setopt(pycurl.NOSIGNAL, True)
+            if self.speed != 0:
+                c.setopt(pycurl.MAX_RECV_SPEED_LARGE, self.speed)
+            c.setopt(pycurl.POSTFIELDS, str("streamKey=" + key["result"]["%s" % self.song.get_id()]["streamKey"]))
+            c.perform()
+            self.file.close()
+            self.song.set_download_progress(100)
+            self.song.emit("download-completed")
+        except pycurl.error, e:
+            if e[0] == pycurl.E_ABORTED_BY_CALLBACK:
+                self.file.close()
+                self.song.emit("download-canceled")
+            else:
+                self.file.close()
+                os.remove(self.song.get_filename())
+        except Exception, e:
+            self.file.close()
+            os.remove(self.song.get_filename())
+            self.song.emit("download-error", e.args)
+
+    def stop(self, option = 'cancel'):
+        # TODO: This one should be checked how to resolve, at the moment
+        # stopping the thread terminates the program
+        self._stop.set()
+
+    def stopped(self):
+        return self._stop.isSet()
+
+    def hook(self, downloadTotal, downloadCurrent, uploadTotal, uploadCurrent):
+        if downloadTotal > 0:
+            if self.first_hook:
+                self.first_hook = False
+                self.song.emit("download-started")
+            
+            progress = (downloadCurrent / downloadTotal) * 100
+            self.song.set_download_progress(progress)
+            self.song.set_file_size(downloadTotal)
+
+            if progress <= 99:
+                self.song.emit("download-updated")
+
+        if self.stopped():
+            return True
+        else:
+            return False

File lib/tfuncs.py

         return self._stop.isSet()
 
 
-class DownloadThread(threading.Thread):
-
-    def __init__(self, _window, _song, _filename, _speed = 0, _continue = None):
-        """
-        _window: The main window object
-        _song: The song to download
-        _filename: The full path file name to download the song
-        """
-        threading.Thread.__init__(self)
-
-        self.win = _window
-
-        self.speed = _speed
-        self.option = None
-        self.cont = _continue
-
-        self.filename = _filename
-
-        try:
-            if self.cont == None:
-                self.file = open(self.filename, "w")
-            else:
-                self.file = open(self.filename, "ab")
-        except IOError:
-            error = guihelpers.ErrorMessage(None,
-                _("Failed to create '%s' for writing.") % self.filename)
-            error.show_all()
-            raise
-
-        self.song = _song
-        self.songid = _song["SongID"]
-        self._stop = threading.Event()
-
-        if self.cont == None:
-            self.iter = self.win.downloads.append([unicode(os.path.basename(self.filename), errors = 'replace'),
-                                               self.songid, 0, "", self])
-            self.win.downqueue.append({'iter': self.iter, 'song': self.song, 'filename': self.filename})
-        else:
-            self.iter = self.win.downqueue[self.cont]['iter']
-
-        self.win.update_downloads_count()
-
-    def run(self):
-        try:
-            key = groove.getStreamKeyFromSongIDEx(self.songid)
-        except Exception, e:
-            print e
-            return
-
-        try:
-            url = "http://" + key["result"]["%s" % self.songid]["ip"] + "/stream.php"
-            url = str(url)
-            c = pycurl.Curl()
-            c.setopt(pycurl.URL, url)
-            c.setopt(pycurl.NOPROGRESS, 0)
-            c.setopt(pycurl.PROGRESSFUNCTION, self.hook)
-            c.setopt(pycurl.FAILONERROR, True)
-            if self.cont == None:
-                c.setopt(pycurl.FILE, self.file)
-            else:
-                c.setopt(pycurl.RESUME_FROM, os.path.getsize(self.filename))
-                c.setopt(pycurl.WRITEDATA, self.file)
-            c.setopt(pycurl.POST, True)
-            c.setopt(pycurl.NOSIGNAL, True)
-            if self.speed != 0:
-                c.setopt(pycurl.MAX_RECV_SPEED_LARGE, self.speed)
-            c.setopt(pycurl.POSTFIELDS, str("streamKey=" + key["result"]["%s" % self.songid]["streamKey"]))
-            c.perform()
-            self.file.close()
-        except pycurl.error, e:
-            if e[0] == 42 and self.option == "stop":
-                self.file.close()
-            else:
-                self.file.close()
-                os.remove(self.filename)
-        except Exception, e:
-            print "Exception ", e
-            self.file.close()
-            os.remove(self.filename)
-            print e.args
-        finally:
-            #self._remove_download()
-            if self.option == "stop":
-                pass
-            else:
-                gobject.idle_add(self._remove_download)
-
-    def stop(self, option = 'cancel'):
-        # TODO: This one should be checked how to resolve, at the moment
-        # stopping the thread terminates the program
-        self.option = option
-        self._stop.set()
-
-    def stopped(self):
-        return self._stop.isSet()
-
-    def hook(self, downloadTotal, downloadCurrent, uploadTotal, uploadCurrent):
-        if self.iter != None and downloadTotal > 0:
-            progress = (downloadCurrent / downloadTotal) * 100
-
-            if progress < 99:
-                mb = 1024 ** 2
-                self.set_value(2, progress)
-                self.set_value(3, "%.02f MB" % (downloadTotal / mb))
-
-        if self._stop.isSet():
-            return True
-        else:
-            return False
-
-    def _remove_download(self):
-        self.win.downloads.remove(self.iter)
-        for i in range(len(self.win.downqueue)):
-            if self.iter == self.win.downqueue[i]['iter']:
-                self.win.downqueue.pop(i)
-        self.iter = None
-        self.win.update_downloads_count()
-
-    def set_value(self, column, value):
-        try:
-            self.win.downloads.set_value(self.iter, column, value)
-        except:
-            pass
-
-
 class PlayThread(threading.Thread):
 
     def __init__(self, _window, _song, _conf, _lastfm):