gget r13 - in trunk: data gget



Author: johans
Date: Mon Jun 23 08:35:59 2008
New Revision: 13
URL: http://svn.gnome.org/viewvc/gget?rev=13&view=rev

Log:
More work on metalink support. A lot of things still missing.

Added:
   trunk/gget/DownloadManager.py
Modified:
   trunk/data/gget.glade
   trunk/gget/AddDownloadDialog.py
   trunk/gget/Download.py
   trunk/gget/Main.py
   trunk/gget/MainWindow.py
   trunk/gget/Makefile.am
   trunk/gget/Utils.py

Modified: trunk/data/gget.glade
==============================================================================
--- trunk/data/gget.glade	(original)
+++ trunk/data/gget.glade	Mon Jun 23 08:35:59 2008
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
-<!--Generated with glade3 3.4.5 on Thu Jun 19 02:03:34 2008 -->
+<!--Generated with glade3 3.4.5 on Mon Jun 23 08:26:27 2008 -->
 <glade-interface>
   <widget class="GtkWindow" id="main_window">
     <property name="width_request">800</property>
@@ -56,27 +56,6 @@
                   <widget class="GtkMenu" id="menu2">
                     <property name="visible">True</property>
                     <child>
-                      <widget class="GtkMenuItem" id="select_all_menu_item">
-                        <property name="visible">True</property>
-                        <property name="label" translatable="yes">Select all</property>
-                        <property name="use_underline">True</property>
-                        <accelerator key="a" modifiers="GDK_CONTROL_MASK" signal="activate"/>
-                      </widget>
-                    </child>
-                    <child>
-                      <widget class="GtkMenuItem" id="unselect_all_menu_item">
-                        <property name="visible">True</property>
-                        <property name="label" translatable="yes">Unselect all</property>
-                        <property name="use_underline">True</property>
-                        <accelerator key="a" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK" signal="activate"/>
-                      </widget>
-                    </child>
-                    <child>
-                      <widget class="GtkSeparatorMenuItem" id="separatormenuitem2">
-                        <property name="visible">True</property>
-                      </widget>
-                    </child>
-                    <child>
                       <widget class="GtkImageMenuItem" id="preferences_menu_item">
                         <property name="visible">True</property>
                         <property name="label" translatable="yes">gtk-preferences</property>
@@ -237,25 +216,16 @@
                     <property name="column_spacing">12</property>
                     <property name="row_spacing">6</property>
                     <child>
-                      <widget class="GtkLabel" id="label3">
-                        <property name="visible">True</property>
-                        <property name="xalign">1</property>
-                        <property name="label" translatable="yes">_URL:</property>
-                        <property name="use_underline">True</property>
-                        <property name="mnemonic_widget">url_entry</property>
-                      </widget>
-                      <packing>
-                        <property name="x_options">GTK_FILL</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <widget class="GtkEntry" id="url_entry">
+                      <widget class="GtkFileChooserButton" id="download_filechooserbutton">
                         <property name="visible">True</property>
-                        <property name="can_focus">True</property>
+                        <property name="action">GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER</property>
+                        <property name="title" translatable="yes">Select download folder</property>
                       </widget>
                       <packing>
                         <property name="left_attach">1</property>
                         <property name="right_attach">2</property>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
                       </packing>
                     </child>
                     <child>
@@ -273,16 +243,25 @@
                       </packing>
                     </child>
                     <child>
-                      <widget class="GtkFileChooserButton" id="download_filechooserbutton">
+                      <widget class="GtkEntry" id="url_entry">
                         <property name="visible">True</property>
-                        <property name="action">GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER</property>
-                        <property name="title" translatable="yes">Select download folder</property>
+                        <property name="can_focus">True</property>
                       </widget>
                       <packing>
                         <property name="left_attach">1</property>
                         <property name="right_attach">2</property>
-                        <property name="top_attach">1</property>
-                        <property name="bottom_attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkLabel" id="label3">
+                        <property name="visible">True</property>
+                        <property name="xalign">1</property>
+                        <property name="label" translatable="yes">_URL:</property>
+                        <property name="use_underline">True</property>
+                        <property name="mnemonic_widget">url_entry</property>
+                      </widget>
+                      <packing>
+                        <property name="x_options">GTK_FILL</property>
                       </packing>
                     </child>
                   </widget>
@@ -550,8 +529,8 @@
                                           <widget class="GtkFileChooserButton" id="default_folder_filechooserbutton">
                                             <property name="visible">True</property>
                                             <property name="sensitive">False</property>
-                                            <property name="action">GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER</property>
                                             <property name="local_only">False</property>
+                                            <property name="action">GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER</property>
                                             <property name="title" translatable="yes">Select download folder</property>
                                           </widget>
                                         </child>

Modified: trunk/gget/AddDownloadDialog.py
==============================================================================
--- trunk/gget/AddDownloadDialog.py	(original)
+++ trunk/gget/AddDownloadDialog.py	Mon Jun 23 08:35:59 2008
@@ -23,11 +23,11 @@
 import gtk
 
 import GUI
+from DownloadManager import DownloadManager
 from gget import NAME
 
 class AddDownloadDialog:
-    def __init__(self, main_window, config):
-        self.main_window = main_window
+    def __init__(self, config):
         self.config = config
 
         self.__get_widgets()
@@ -72,8 +72,9 @@
     def __set_url_from_clipboard(self, clipboard):
         if clipboard.wait_is_text_available():
             url = self.clipboard.wait_for_text()
-            if self.__is_valid_url(url):
+            if url and self.__is_valid_url(url):
                 self.url_entry.set_text(url)
+                # self.url_entry.select_region(0, -1)
 
     def __is_valid_url(self, url):
         PROTOCOLS = ["http", "https", "ftp"]
@@ -88,7 +89,8 @@
         self.add_button.clicked()
 
     def __add_button_clicked(self, button):
-        self.main_window.start_download(self.url_entry.get_text(),
+        download_manager = DownloadManager()
+        download_manager.start_download(self.url_entry.get_text(),
                 self.download_filechooserbutton.get_current_folder())
         self.dialog.hide()
 

Modified: trunk/gget/Download.py
==============================================================================
--- trunk/gget/Download.py	(original)
+++ trunk/gget/Download.py	Mon Jun 23 08:35:59 2008
@@ -19,14 +19,11 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
 import os.path
-import thread
 from gettext import gettext as _
 
 import gtk
 import gobject
 
-import metalink
-
 import Utils
 import GUI
 from gget import NAME
@@ -38,18 +35,26 @@
                                 where to save the download", "",
                                 gobject.PARAM_READWRITE)}
 
-    __gsignals__ = {"finished": (gobject.SIGNAL_RUN_LAST, None, (object,)),
-                    "update":   (gobject.SIGNAL_RUN_LAST, None, (int, int,
-                        int))}
+    __gsignals__ = {"update":   (gobject.SIGNAL_RUN_LAST, None, (int, int,
+                                 int)),
+                    "speed-changed": (gobject.SIGNAL_RUN_LAST, None, (int,)),
+                    "status-changed": (gobject.SIGNAL_RUN_LAST, None, (int,))}
 
     def __init__(self, url, path):
+        gobject.GObject.__init__(self)
         self.url = url
         self.path = path
 
         self.file_name = os.path.basename(self.url)
+
         self.size = 0
+        self.block_count = 0
+        self.block_size = 0
         self.percent_complete = 0
 
+    def __str__(self):
+        return self.url
+
     def do_get_property(self, property):
         if property.name == "url":
             return self.url
@@ -62,28 +67,26 @@
         elif property.name == "path":
             self.path = path
 
-    def start(self):
-        if os.path.exists(os.path.join(self.path, self.file_name)):
-            pass
-            # TODO: Overwrite dialog
-        else:
-            thread.start_new_thread(self.__start_in_thread, ())
-
-    def __start_in_thread(self):
-        metalink.get(self.url, self.path, self.__update)
-
-    def __update(self, block_count, block_size, total_size):
-        Utils.debug_print("Update called!")
+    def update(self, block_count, block_size, total_size):
+        Utils.debug_print("Download.update called with block_count: %s block_size: %s total_size: %s" % (block_count, block_size, total_size))
+        self.block_count = block_count
+        self.block_size = block_size
         self.size = total_size
+
+        current_bytes = float(block_count * block_size) / 1024 / 1024
+        total_bytes = float(total_size) / 1024 / 1024
         try:
-            self.percent_complete = 100 * float(block_count * block_size) / float(total_size)
+            self.percent_complete = 100 * current_bytes / total_bytes
         except ZeroDivisionError:
             self.percent_complete = 0
 
-        if percent_complete > 100:
-            percent_complete = 100
+        if self.percent_complete > 100:
+            self.percent_complete = 100
 
-        self.emit("update", block_count, block_size, total_size)
+        Utils.debug_print("Percent complete: %s" % self.percent_complete)
 
+        gtk.gdk.threads_enter()
+        self.emit("update", block_count, block_size, total_size)
+        gtk.gdk.threads_leave()
 
 # vim: set sw=4 et sts=4 tw=79 fo+=l:

Added: trunk/gget/DownloadManager.py
==============================================================================
--- (empty file)
+++ trunk/gget/DownloadManager.py	Mon Jun 23 08:35:59 2008
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2008 Johan Svedberg <johan svedberg com>
+
+# This file is part of gget.
+
+# gget is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# gget is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with gget; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+
+import os.path
+import sys
+import thread
+from gettext import gettext as _
+
+import gtk
+import gobject
+
+import metalink
+
+import Utils
+import GUI
+from Download import Download
+from Configuration import Configuration
+from gget import NAME, VERSION
+
+class DownloadManager(gobject.GObject):
+    """Singleton handling the downloads"""
+
+    __gsignals__ = {"download-added": (gobject.SIGNAL_RUN_LAST, None,
+        (object,))}
+
+    instance = None
+
+    def __new__(type, *args):
+        if DownloadManager.instance is None:
+            DownloadManager.instance = gobject.GObject.__new__(type)
+            DownloadManager.instance.__init(*args)
+        return DownloadManager.instance
+
+    def __init(self, *args):
+        gobject.GObject.__init__(self)
+        self.config = Configuration()
+        self.downloads = []
+
+        metalink.USER_AGENT = "%s %s" % (NAME, VERSION)
+
+        # TODO: Get this from system wide settings
+        # metalink.HTTP_PROXY = ""
+        # metalink.HTTPS_PROXY = ""
+        # metalink.FTP_PROXY = ""
+
+    def start_download(self, uri, path=None):
+        if path is None:
+            path = self.config.default_folder
+        download = Download(uri, path)
+
+        Utils.debug_print("Starting download %s" % download)
+        self.downloads.append(download)
+        result = thread.start_new_thread(self.__start_download_in_thread,
+                (download,))
+        self.emit("download-added", (download))
+        # self.__start_download_in_thread(download)
+        if not result:
+            print "Failed downloading of file %s" % download.url
+
+    def __start_download_in_thread(self, download):
+        # Python 2.5 seems to have a bug: sys.excepthook is not call from code
+        # in a thread, see http://spyced.blogspot.com/2007/06/workaround-for-sysexcepthook-bug.html
+        # sys.excepthook(*sys.exc_info())
+
+        metalink.get(download.url, download.path, handler=download.update)
+
+
+# vim: set sw=4 et sts=4 tw=79 fo+=l:

Modified: trunk/gget/Main.py
==============================================================================
--- trunk/gget/Main.py	(original)
+++ trunk/gget/Main.py	Mon Jun 23 08:35:59 2008
@@ -30,6 +30,7 @@
 import gnome
 
 import GUI
+from DownloadManager import DownloadManager
 from MainWindow import MainWindow
 from StatusIcon import StatusIcon
 from Configuration import Configuration
@@ -56,12 +57,13 @@
             print_usage()
 
     gnome.init(NAME, VERSION)
-    # gtk.gdk.threads_init()
+    gtk.gdk.threads_init()
     gtk.window_set_default_icon_list(*GUI.get_icon_list([16, 22, 24, 32]))
 
     config = Configuration(debug)
+    download_manager = DownloadManager()
 
-    main_window = MainWindow(config)
+    main_window = MainWindow(config, download_manager)
     if config.show_main_window:
         main_window.window.show()
 
@@ -69,10 +71,15 @@
     if not config.show_status_icon:
         status_icon.icon.set_visible(False)
 
+    # sys.excepthook = main_window.on_unhandled_exception
+
+    for uri in args:
+        download_manager.start_download(uri)
+
     gtk.main()
 
 def print_usage():
-    print _("Usage: %s [OPTION]...") % (sys.argv[0])
+    print _("Usage: %s [OPTION]... [URI]...") % (sys.argv[0])
     print ""
     print _("OPTIONS:")
     print "  -d, --debug		%s" % (_("enable debug messages"))

Modified: trunk/gget/MainWindow.py
==============================================================================
--- trunk/gget/MainWindow.py	(original)
+++ trunk/gget/MainWindow.py	Mon Jun 23 08:35:59 2008
@@ -18,6 +18,8 @@
 # along with gget; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
+import sys
+import traceback
 from gettext import gettext as _
 
 import gtk
@@ -25,15 +27,17 @@
 
 import GUI
 import Utils
+from GUI import ErrorDialog
 from AboutDialog import AboutDialog
 from AddDownloadDialog import AddDownloadDialog
-from Download import Download
 from PreferencesDialog import PreferencesDialog
 from gget import NAME
 
 class MainWindow:
-    def __init__(self, config):
+    def __init__(self, config, download_manager):
         self.config = config
+        self.download_manager = download_manager
+        self.download_manager.connect("download-added", self.__download_added)
 
         self.__get_widgets()
 
@@ -42,6 +46,7 @@
         self.__connect_widgets()
 
     def __get_widgets(self):
+        """Get widgets from the glade file."""
         xml = gtk.glade.XML(GUI.glade_file, domain=NAME.lower())
 
         self.window = xml.get_widget("main_window")
@@ -51,8 +56,6 @@
         self.quit_menu_item = xml.get_widget("quit_menu_item")
 
         # Edit menu
-        self.select_all_menu_item = xml.get_widget("select_all_menu_item")
-        self.unselect_all_menu_item = xml.get_widget("unselect_all_menu_item")
         self.preferences_menu_item = xml.get_widget("preferences_menu_item")
 
         # Help menu
@@ -68,9 +71,9 @@
         self.statusbar = xml.get_widget("statusbar")
 
     def __make_downloads_treeview(self):
+        """Constructs the treeview containing downloads."""
         self.downloads_model = gtk.ListStore(object)
         self.downloads_treeview.set_model(self.downloads_model)
-        self.downloads_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
 
         # Name
         name_cell_renderer = gtk.CellRendererText()
@@ -122,27 +125,49 @@
                 self.__speed_cell_data_func)
         self.downloads_treeview.append_column(speed_treeview_column)
 
+        # ETA
+        eta_cell_renderer = gtk.CellRendererText()
+        eta_cell_renderer.props.xpad = 3
+        eta_cell_renderer.props.ypad = 3
+        eta_treeview_column = gtk.TreeViewColumn(_("ETA"),
+                eta_cell_renderer)
+        eta_treeview_column.set_cell_data_func(eta_cell_renderer,
+                self.__eta_cell_data_func)
+        self.downloads_treeview.append_column(eta_treeview_column)
+
     def __name_cell_data_func(self, column, cell, model, iter):
+        """Data function for the name of downloads."""
         download = model.get_value(iter, 0)
         cell.props.text = download.file_name
 
     def __status_cell_data_func(self, column, cell, model, iter):
+        """Data function for the status of downloads."""
         download = model.get_value(iter, 0)
         cell.props.text = "N/A"
 
     def __size_cell_data_func(self, column, cell, model, iter):
+        """Data function for the file size of downloads."""
         download = model.get_value(iter, 0)
         cell.props.text = Utils.get_readable_size(download.size)
 
     def __progress_cell_data_func(self, column, cell, model, iter):
+        """Data function for the progress bar of downloads."""
         download = model.get_value(iter, 0)
         cell.props.value = download.percent_complete
+        cell.props.text = _("%s of %s complete") % (Utils.get_readable_size(float(download.block_count * download.block_size)), Utils.get_readable_size(download.size))
 
     def __speed_cell_data_func(self, column, cell, model, iter):
+        """Data function for the speed of downloads."""
+        download = model.get_value(iter, 0)
+        cell.props.text = "N/A"
+
+    def __eta_cell_data_func(self, column, cell, model, iter):
+        """Data function for estemated time of arrival (ETA) of downloads."""
         download = model.get_value(iter, 0)
         cell.props.text = "N/A"
 
     def __connect_widgets(self):
+        """Connect widgets to various signals."""
         self.window.connect("destroy", self.quit)
 
         # File menu
@@ -150,10 +175,6 @@
         self.quit_menu_item.connect("activate", self.quit)
 
         # Edit menu
-        self.select_all_menu_item.connect("activate",
-                self.__select_all_menu_item_activate)
-        self.unselect_all_menu_item.connect("activate",
-                self.__unselect_all_menu_item_activate)
         self.preferences_menu_item.connect("activate",
                 self.preferences_menu_item_activate)
 
@@ -164,34 +185,76 @@
         # Toolbar
         self.add_tool_button.connect("clicked", self.show_add_download_dialog)
 
+        selection = self.downloads_treeview.get_selection()
+        selection.connect("changed",
+                self.__downloads_treeview_selection_changed)
+
     def show_add_download_dialog(self, widget):
-        add = AddDownloadDialog(self, self.config)
+        """Show the dialog used for adding a new download."""
+        add = AddDownloadDialog(self.config)
         add.dialog.show()
 
     def preferences_menu_item_activate(self, menu_item):
+        """Show the preferences dialog."""
         pd = PreferencesDialog(self.config)
         pd.dialog.show()
 
-    def __select_all_menu_item_activate(self, menu_item):
-        selection = self.downloads_treeview.get_selection()
-        selection.select_all()
-
-    def __unselect_all_menu_item_activate(self, menu_item):
-        selection = self.downloads_treeview.get_selection()
-        selection.unselect_all()
-
     def __about_menu_item_activate(self, menu_item):
+        """Show the about dialog."""
         ad = AboutDialog()
         ad.run()
 
+    def __downloads_treeview_selection_changed(self, selection):
+        (downloads_model, downloads_iter) = selection.get_selected()
+        if downloads_iter:
+            self.pause_tool_button.set_sensitive(True)
+            self.cancel_tool_button.set_sensitive(True)
+        else:
+            self.pause_tool_button.set_sensitive(False)
+            self.cancel_tool_button.set_sensitive(False)
+
     def quit(self, widget):
+        """Quits the application. Called from various places."""
         # TODO: Shutdown gracefully
         gtk.main_quit()
 
-    def start_download(self, url, path):
-        download = Download(url, path)
+    def __download_added(self, download_manager, download):
+        """Called when a new download is added to DownloadManager. Adds the
+        download to the treeview model and sets up the update handler."""
         self.downloads_model.append([download])
+        download.connect("update", self.__download_update)
         GUI.queue_resize(self.downloads_treeview)
-        download.start()
+
+    def __download_update(self, download, block_count, block_size, total_size):
+        """Called on download updates. Finds the associated treeview row and
+        fires a row changed signal."""
+        downloads_iter = self.downloads_model.get_iter_first()
+        for row in self.downloads_model:
+            if row[0] is download:
+                downloads_path = self.downloads_model.get_path(downloads_iter)
+                self.downloads_model.row_changed(downloads_path,
+                        downloads_iter)
+                break
+            downloads_iter = self.downloads_model.iter_next(downloads_iter)
+
+    def on_unhandled_exception(self, type, value, tb):
+        try:
+            list = traceback.format_tb(tb, None) + \
+                    traceback.format_exception_only(type, value)
+            tracelog = '\nTraceback (most recent call last):\n' + "%-20s%s" % \
+                    ("".join(list[:-1]), list[-1])
+
+            message = "An internal program error has occurred."
+            message += "\n" + tracelog
+
+            gtk.gdk.threads_enter()
+            ed = ErrorDialog(_("Unhandled exception"), message)
+            ed.run()
+            ed.destroy()
+            gtk.gdk.threads_leave()
+
+            sys.stderr.write(message)
+        except:
+            traceback.print_exc()
 
 # vim: set sw=4 et sts=4 tw=79 fo+=l:

Modified: trunk/gget/Makefile.am
==============================================================================
--- trunk/gget/Makefile.am	(original)
+++ trunk/gget/Makefile.am	Mon Jun 23 08:35:59 2008
@@ -4,6 +4,7 @@
 	AddDownloadDialog.py	\
 	Configuration.py	\
 	Download.py		\
+	DownloadManager.py	\
 	GUI.py			\
 	__init__.py		\
 	metalink.py		\

Modified: trunk/gget/Utils.py
==============================================================================
--- trunk/gget/Utils.py	(original)
+++ trunk/gget/Utils.py	Mon Jun 23 08:35:59 2008
@@ -25,13 +25,13 @@
 def get_readable_size(bits):
     for unit in ['bytes','KB','MB','GB','TB']:
         if bits < 1024.0:
-            return "%3.1f%s" % (bits, unit)
+            return "%3.1f %s" % (bits, unit)
         bits /= 1024.0
 
-def debug_print(self, message):
+def debug_print(message):
     config = Configuration()
     if config.debug:
-        print("[%s] %s" % time.strftime("%Y/%m/%d %H:%M:%S", time.localtime()),
-                message)
+        print("[%s] %s" % (time.strftime("%Y/%m/%d %H:%M:%S", time.localtime()),
+                message))
 
 # vim: set sw=4 et sts=4 tw=79 fo+=l:



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]