diff mbox

[Branch,~linaro-image-tools/linaro-image-tools/trunk] Rev 376: Removed GUI code from the FetchImage library.

Message ID 20110714135912.32754.55299.launchpad@loganberry.canonical.com
State Accepted
Headers show

Commit Message

James Tunnicliffe July 14, 2011, 1:59 p.m. UTC
Merge authors:
  James Tunnicliffe (dooferlad)
Related merge proposals:
  https://code.launchpad.net/~dooferlad/linaro-image-tools/remove_gui_from_FetchImage_library/+merge/67950
  proposed by: James Tunnicliffe (dooferlad)
  review: Approve - James Westby (james-w)
------------------------------------------------------------
revno: 376 [merge]
committer: James Tunnicliffe <james.tunnicliffe@linaro.org>
branch nick: linaro-image-tools
timestamp: Thu 2011-07-14 14:57:40 +0100
message:
  Removed GUI code from the FetchImage library.
  Updated the download status the UI gives to the user, so now it is integrated into the wizard.
modified:
  fetch_image_ui.py
  linaro_image_tools/FetchImage.py


--
lp:linaro-image-tools
https://code.launchpad.net/~linaro-image-tools/linaro-image-tools/trunk

You are subscribed to branch lp:linaro-image-tools.
To unsubscribe from this branch go to https://code.launchpad.net/~linaro-image-tools/linaro-image-tools/trunk/+edit-subscription
diff mbox

Patch

=== modified file 'fetch_image_ui.py'
--- fetch_image_ui.py	2011-06-17 18:29:14 +0000
+++ fetch_image_ui.py	2011-07-14 12:50:56 +0000
@@ -31,6 +31,8 @@ 
 import unittest
 import operator
 import Queue
+import time
+import datetime
 
 
 def add_button(bind_to,
@@ -43,9 +45,9 @@ 
 
     """Create a radio button with event bindings."""
     if(style != None):
-        radio_button = wx.RadioButton(bind_to, label = label, style = style)
+        radio_button = wx.RadioButton(bind_to, label=label, style=style)
     else:
-        radio_button = wx.RadioButton(bind_to, label = label)
+        radio_button = wx.RadioButton(bind_to, label=label)
 
     sizer.Add(radio_button, 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5)
     bind_to.Bind(wx.EVT_RADIOBUTTON, select_event, radio_button)
@@ -126,9 +128,9 @@ 
         self.box2 = wx.BoxSizer(wx.VERTICAL)
 
         header = wx.StaticText(self,
-                               label = "Please select the hardware that you "
-                                       "would like to build an image for from "
-                                       "the following list")
+                               label="Please select the hardware that you "
+                                     "would like to build an image for from "
+                                     "the following list")
 
         header.Wrap(width - 10)  # -10 because boarder below is 5 pixels wide
 
@@ -143,11 +145,12 @@ 
         self.settings['hardware'] = default_hardware
         self.settings['compatable_hwpacks'] = (
                 self.settings['choice']['hwpack'][self.settings['hardware']])
-        
+
         self.cb_hardware = wx.ComboBox(self,
-                            value =
-                            self.settings['choice']['hardware'][default_hardware],
-                            style = wx.CB_DROPDOWN | wx.CB_READONLY)
+                            value=self.settings['choice']
+                                               ['hardware']
+                                               [default_hardware],
+                            style=wx.CB_DROPDOWN | wx.CB_READONLY)
 
         self.Bind(wx.EVT_COMBOBOX,
                   self.event_combo_box_hardware,
@@ -201,8 +204,8 @@ 
         self.sizer = wx.BoxSizer(wx.VERTICAL)
         self.wizard = parent
 
-        header = wx.StaticText(self, label = "Please select the stable Linaro "
-                                             "release you would like to use")
+        header = wx.StaticText(self, label="Please select the stable Linaro "
+                                           "release you would like to use")
 
         header.Wrap(width - 10)  # -10 because boarder below is 5 pixels wide
 
@@ -215,8 +218,8 @@ 
 
         default_release = self.settings['UI']['translate'][platforms[-1]]
         self.cb_release = wx.ComboBox(self,
-                                      value = default_release,
-                                      style = wx.CB_DROPDOWN | wx.CB_READONLY)
+                                      value=default_release,
+                                      style=wx.CB_DROPDOWN | wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX,
                   self.event_combo_box_release,
                   self.cb_release)
@@ -234,12 +237,12 @@ 
             self.cb_release.Append(item, item.upper())
 
         self.cb_build = wx.ComboBox(self,
-                                    style = wx.CB_DROPDOWN | wx.CB_READONLY)
+                                    style=wx.CB_DROPDOWN | wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_build, self.cb_build)
 
         self.box1.Add(self.cb_release, 0,
                       wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5)
-        self.box1.Add(self.cb_build,   0,
+        self.box1.Add(self.cb_build, 0,
                       wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5)
         self.sizer.Add(self.box1, 0, wx.ALIGN_LEFT | wx.ALL, 5)
         self.SetSizerAndFit(self.sizer)
@@ -255,7 +258,7 @@ 
         self.cb_build.SetValue("No build available")
 
         for build in builds:
-            if( self.db.hardware_is_available_for_platform_build(
+            if(self.db.hardware_is_available_for_platform_build(
                                           self.settings['compatable_hwpacks'],
                                           self.settings['platform'],
                                           build)
@@ -336,12 +339,12 @@ 
         self.sizer = wx.BoxSizer(wx.VERTICAL)
 
         header = wx.StaticText(self,
-                               label = "Builds are created most days. First "
-                                       "please select the day on which the "
-                                       "build you would like to use was built,"
-                                       " then, if there was more than one "
-                                       "build that day you will be able to "
-                                       "select the build number.")
+                               label="Builds are created most days. First "
+                                     "please select the day on which the "
+                                     "build you would like to use was built,"
+                                     " then, if there was more than one "
+                                     "build that day you will be able to "
+                                     "select the build number.")
         header.Wrap(width - 10)  # -10 because boarder below is 5 pixels wide
 
         box1 = wx.BoxSizer(wx.VERTICAL)
@@ -353,8 +356,8 @@ 
         self.today.SetToCurrent()
         self.settings['build_date'] = self.today.FormatISODate().encode('ascii')
 
-        dpc = wx.DatePickerCtrl(self, size = (120, -1),
-                                style = wx.DP_DEFAULT)
+        dpc = wx.DatePickerCtrl(self, size=(120, -1),
+                                style=wx.DP_DEFAULT)
         self.Bind(wx.EVT_DATE_CHANGED, self.on_date_changed, dpc)
 
         #--- Build number Combo Box ---
@@ -363,7 +366,7 @@ 
         self.settings['build_number'] = 0
         self.update_build()
         self.cb_build = wx.ComboBox(self,
-                                    style = wx.CB_DROPDOWN | wx.CB_READONLY)
+                                    style=wx.CB_DROPDOWN | wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_build, self.cb_build)
 
         #--- Layout ---
@@ -493,16 +496,16 @@ 
         self.sizer = wx.BoxSizer(wx.VERTICAL)
         self.settings['image'] = None
 
-        header = wx.StaticText(self, label = "Please select the operating "
-                                             "system you would like to run on "
-                                             "your hardware.")
+        header = wx.StaticText(self, label="Please select the operating "
+                                           "system you would like to run on "
+                                           "your hardware.")
         header.Wrap(width - 10)  # -10 because boarder below is 5 pixels wide
 
         self.box1 = wx.BoxSizer(wx.VERTICAL)
         self.sizer.Add(header)
 
         self.cb_image = wx.ComboBox(self,
-                                    style = wx.CB_DROPDOWN | wx.CB_READONLY)
+                                    style=wx.CB_DROPDOWN | wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_os, self.cb_image)
 
         #--- Layout ---
@@ -679,7 +682,7 @@ 
         self.settings['path_selected'] = ""
 
         header = wx.StaticText(self,
-                               label = "Media Creation Settings\n\n"
+                               label="Media Creation Settings\n\n"
                                "Please select if you would like to write the "
                                "file system I am about to create to a memory "
                                "card, or to a file on the local file system.")
@@ -691,8 +694,8 @@ 
         default_target = file_systems[0]
         self.settings['rootfs'] = default_target
         cb_rootfs = wx.ComboBox(self,
-                                value = default_target,
-                                style = wx.CB_DROPDOWN | wx.CB_READONLY)
+                                value=default_target,
+                                style=wx.CB_DROPDOWN | wx.CB_READONLY)
 
         for item in file_systems:
             cb_rootfs.Append(item, item.upper())
@@ -737,14 +740,14 @@ 
 
         self.help_text_values = {"device": "Please select a device to write "
                                            "the file system to:",
+
                                  "file":   "Please select a file to write the "
                                            "file system to:"}
 
         self.help_text = wx.StaticText(
                              self,
-                             label =
-                             self.help_text_values[
-                                   self.settings['write_to_file_or_device']])
+                             label=self.help_text_values[
+                                     self.settings['write_to_file_or_device']])
         self.help_text.Wrap(width - 10)
 
         #-- File/dev picker --
@@ -774,7 +777,7 @@ 
         #-- Combo boxes for hardware and image selection --
         optional_settings_box_title = wx.StaticBox(
                                                 self,
-                                                label = " Optional Settings ")
+                                                label=" Optional Settings ")
 
         self.optional_settings_box = wx.StaticBoxSizer(
                                                 optional_settings_box_title,
@@ -790,7 +793,7 @@ 
         grid1.Add(cb_rootfs, 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5)
 
         grid1.Add(wx.StaticText(self,
-                                label = "The root file system of the image"),
+                                label="The root file system of the image"),
                                 0,
                                 wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                                 5)
@@ -811,14 +814,14 @@ 
         # Add a choice of MB or GB for size input
         units = ["GB", "MB"]
         self.size_unit = units[0]  # Set the default unit
-        unit_choice = wx.Choice(self, -1, (100, 50), choices = units)
+        unit_choice = wx.Choice(self, -1, (100, 50), choices=units)
         self.Bind(wx.EVT_CHOICE, self.event_chose_unit, unit_choice)
         file_size_grid.Add(unit_choice, 0, wx.ALIGN_RIGHT | wx.TOP, 5)
 
         # Back out of the extra grid, add some help text
         grid1.Add(wx.StaticText(
                             self,
-                            label = "Writing to file only: Image file size"),
+                            label="Writing to file only: Image file size"),
                             0,
                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                             5)
@@ -829,15 +832,15 @@ 
                   wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                   5)
 
-        grid1.Add(wx.StaticText(self, label = "Swap file size in MB"),
+        grid1.Add(wx.StaticText(self, label="Swap file size in MB"),
                   0,
                   wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                   5)
 
         self.cb_hwpacks = wx.ComboBox(
                                 self,
-                                value = self.settings['compatable_hwpacks'][0],
-                                style = wx.CB_DROPDOWN | wx.CB_READONLY)
+                                value=self.settings['compatable_hwpacks'][0],
+                                style=wx.CB_DROPDOWN | wx.CB_READONLY)
 
         self.Bind(wx.EVT_COMBOBOX,
                   self.event_combo_box_hwpack,
@@ -848,14 +851,14 @@ 
                   wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                   5)
 
-        grid1.Add(wx.StaticText(self, label = "Compatible hardware packs"),
+        grid1.Add(wx.StaticText(self, label="Compatible hardware packs"),
                   0,
                   wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                   5)
 
         self.optional_settings_box.Add(grid1, 0, wx.ALIGN_LEFT | wx.ALL, 5)
 
-        confirm_mmc_usage_title = wx.StaticBox(self, label = " Are you sure? ")
+        confirm_mmc_usage_title = wx.StaticBox(self, label=" Are you sure? ")
 
         self.confirm_mmc_usage_box = wx.StaticBoxSizer(confirm_mmc_usage_title,
                                                        wx.VERTICAL)
@@ -1031,7 +1034,7 @@ 
         self.width = width
         self.wizard = parent
 
-        header = wx.StaticText(self, label = """Installing...""")
+        header = wx.StaticText(self, label="""Installing...""")
         header.Wrap(width - 10)  # -10 because boarder below is 5 pixels wide
 
         self.sizer.Add(header)
@@ -1039,7 +1042,7 @@ 
 
         # We expect to print 4 lines of information, reserve space using blank
         # lines.
-        self.settings_summary_text = wx.StaticText(self, label = "\n\n\n\n")
+        self.settings_summary_text = wx.StaticText(self, label="\n\n\n\n")
         self.settings_summary_text.Wrap(width - 10)
 
         self.box1.Add(self.settings_summary_text, 0, wx.ALIGN_LEFT | wx.ALL, 5)
@@ -1053,7 +1056,12 @@ 
         self.start_button.SetSize(self.start_button.GetBestSize())
         self.box1.Add(self.start_button, 0, wx.ALIGN_LEFT | wx.ALL, 5)
 
-        self.status_grid = wx.FlexGridSizer(0, 2, 0, 0)
+        self.download_guage = wx.Gauge(self,
+                                       -1,
+                                       1000,
+                                       size=(self.width * 2 / 3, 25))
+
+        self.status_grid = wx.FlexGridSizer(0, 2)
 
         self.status_grid.Add(wx.StaticText(self, label="Downloading files"),
                              0,
@@ -1061,11 +1069,24 @@ 
                              5)
 
         self.downloading_files_status = wx.StaticText(self, label="")
+
         self.status_grid.Add(self.downloading_files_status,
                              0,
                              wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                              5)
 
+        self.status_grid.Add(self.download_guage,
+                             0,
+                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
+                             5)
+
+        self.downloading_files_info = wx.StaticText(self, label="")
+
+        self.status_grid.Add(self.downloading_files_info,
+                             0,
+                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
+                             5)
+
         self.status_grid.Add(wx.StaticText(self, label="Unpacking downloads"),
                              0,
                              wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
@@ -1147,7 +1168,7 @@ 
 
         settings_summary += (  "Writing image to "
                              + self.settings['write_to_file_or_device']
-                             + " " 
+                             + " "
                              + path)
 
         self.settings_summary_text.SetLabel(settings_summary)
@@ -1175,29 +1196,26 @@ 
 
         if(image_url and hwpack_url):
 
-            print image_url
-            print hwpack_url
-
             self.file_handler = FetchImage.FileHandler()
 
+            self.timer = wx.Timer(self)
+            self.Bind(wx.EVT_TIMER, self.timer_ping, self.timer)
+            self.timer.Start(milliseconds=100, oneShot=True)
+
             tools_dir = os.path.dirname(__file__)
             if tools_dir == '':
                 tools_dir = None
 
-            self.file_handler.create_media(image_url,
-                                           hwpack_url,
-                                           self.settings,
-                                           tools_dir,
-                                           True,
-                                           self)
-
-            self.timer = wx.Timer(self)
-            self.Bind(wx.EVT_TIMER, self.timer_ping, self.timer)
-            self.timer.Start(milliseconds=100, oneShot=True)
-
             self.start_button.Disable()
             self.event_queue = Queue.Queue()
-            self.file_handler.start_lmc_gui_thread(self.event_queue)
+            self.lmc_thread = self.file_handler.LinaroMediaCreate(
+                                                    image_url,
+                                                    hwpack_url,
+                                                    self.file_handler,
+                                                    self.event_queue,
+                                                    self.settings,
+                                                    tools_dir)
+            self.lmc_thread.start()
         else:
             print >> sys.stderr, ("Unable to find files that match the"
                                   "parameters specified")
@@ -1207,27 +1225,28 @@ 
         linaro-media-create every 100ms. This is the function which is called
         to do that polling."""
 
-        if self.event_queue.empty() == False:
+        while not self.event_queue.empty():
             event = self.event_queue.get()
 
             if event[0] == "start":
                 self.event_start(event[1])
-                self.timer.Start(milliseconds=100, oneShot=True)
 
             elif event[0] == "end":
                 self.event_end(event[1])
-                self.timer.Start(milliseconds=100, oneShot=True)
 
             elif event == "terminate":
                 # Process complete. Enable next button.
                 self.wizard.FindWindowById(wx.ID_FORWARD).Enable()
                 self.populate_file_system_status.SetLabel("Done")
+                return  # Even if queue isn't empty, stop processing it
+
+            elif event[0] == "update":
+                self.event_update(event[1], event[2], event[3])
 
             else:
                 print >> sys.stderr, "timer_ping: Unhandled event", event
 
-        else:
-            self.timer.Start(milliseconds=100, oneShot=True)
+        self.timer.Start(milliseconds=50, oneShot=True)
 
     def unsigned_packages_query(self, package_list):
         message = ('In order to continue, I need to install some unsigned'
@@ -1264,7 +1283,7 @@ 
                 self.file_handler.kill_create_media()
                 sys.exit(1)
             else:
-                self.file_handler.send_to_create_process("y")
+                self.lmc_thread.send_to_create_process("y")
 
         elif event == "create file system":
             self.create_file_system_status.SetLabel("Running")
@@ -1289,6 +1308,79 @@ 
         else:
             print "Unhhandled end event:", event
 
+    def event_update(self, task, update_type, value):
+        if task == "download":
+            if update_type == "name":
+                self.downloading_files_status.SetLabel("Downloading")
+                self.old_time = time.time()
+                self.old_bytes_downloaded = 0
+
+            elif update_type == "progress":
+                self.total_bytes_downloaded += value
+
+                time_difference = time.time() - self.old_time
+
+                if time_difference > 1.0:
+                    self.old_time = time.time()
+
+                    # More than a second has passed since we calculated data
+                    # rate
+                    speed = (  float(  self.total_bytes_downloaded
+                                     - self.old_bytes_downloaded)
+                             / time_difference)
+
+                    self.old_bytes_downloaded = self.total_bytes_downloaded
+
+                    self.speeds.append(speed)
+
+                    average_speed = 0
+                    speeds_accumulated = 0
+                    for speed in reversed(self.speeds):
+                        average_speed += speed
+                        speeds_accumulated += 1
+
+                        if speeds_accumulated == 6:
+                            break  # do rolling average of 6 seconds
+
+                    average_speed /= speeds_accumulated
+
+                    time_remaining = (  (  self.total_bytes_to_download
+                                         - self.total_bytes_downloaded)
+                                      / speed)
+
+                    pretty_time = str(datetime.timedelta(seconds=int(
+                                                              time_remaining)))
+
+                    # Following table assumes we don't get past TBps internet
+                    # connections soon :-)
+                    units = ["Bps", "kBps", "MBps", "GBps", "TBps"]
+                    units_index = 0
+                    while speed > 1024:
+                        speed /= 1024
+                        units_index += 1
+
+                    info = "Downloading at {0:.1f} {1}".format(
+                                                         speed,
+                                                         units[units_index])
+
+                    self.downloading_files_status.SetLabel(info)
+
+                    info = "{0} remaining".format(
+                                                         pretty_time)
+
+                    self.downloading_files_info.SetLabel(info)
+
+                self.download_guage.SetValue(  1000
+                                             * self.total_bytes_downloaded
+                                             / self.total_bytes_to_download)
+
+            elif update_type == "total bytes":
+                self.total_bytes_to_download = value
+                self.total_bytes_downloaded = 0
+                self.speeds = []  # keep an array of speeds used to calculate
+                # the estimated time remaining - by not just using the
+                # current speed we can stop the ETA bouncing around too much.
+
     def event_combo_box_release(self, evt):
         pass
 
@@ -1371,14 +1463,14 @@ 
         else:  # Always enable the forward button if reversing into a page
             self.wizard.FindWindowById(wx.ID_FORWARD).Enable()
 
-    def go(self, first_page):
+    def go(self):
         file_handler = FetchImage.FileHandler()
         self.config = FetchImage.FetchImageConfig()
         self.config.settings["force_download"] = False
         self.config.settings['compatable_hwpacks'] = ['foo']
 
         # If the settings file and server index need updating, grab them
-        file_handler.update_files_from_server(show_wx_progress = True)
+        file_handler.update_files_from_server()
 
         # Load settings YAML, which defines the parameters we ask for and
         # acceptable responses from the user
@@ -1446,20 +1538,22 @@ 
         self.wizard.RunWizard(self.pages['release_or_snapshot'])
 
 
-def run(start_page = None):
+def run():
     """Wrapper around the full wizard. Is encapsulated in its own function to
        allow a restart to be performed, as described in __main___, easily"""
     app = wx.PySimpleApp()  # Start the application
-    #logging.basicConfig(level=logging.INFO)
+    if app:
+        pass  # We don't use this directly. Stop pyflakes complaining!
+    
     w = TestDriveWizard('Simple Wizard')
-    return w.go(start_page)
+    return w.go()
 
 
 class TestURLLookupFunctions(unittest.TestCase):
 
     def setUp(self):
         self.file_handler   = FetchImage.FileHandler()
-        self.file_handler.update_files_from_server(show_wx_progress = True)
+        self.file_handler.update_files_from_server()
         self.config         = FetchImage.FetchImageConfig()
         self.config.settings["force_download"] = False
 

=== modified file 'linaro_image_tools/FetchImage.py'
--- linaro_image_tools/FetchImage.py	2011-06-17 18:04:47 +0000
+++ linaro_image_tools/FetchImage.py	2011-07-14 12:50:56 +0000
@@ -52,15 +52,6 @@ 
                                      "image-tools",
                                      "fetch_image")
 
-    class DummyEventHandler():
-        """Just a sink for events if no event handler is provided to
-        create_media"""
-        def event_start(self, event):
-            pass
-
-        def event_end(self, event):
-            pass
-
     def has_key_and_evaluates_True(self, dictionary, key):
         return bool(key in dictionary and dictionary[key])
 
@@ -72,17 +63,15 @@ 
             list.append(setting_name)
             list.append(dictionary[key])
 
-    def create_media(self, image_url, hwpack_url, settings, tools_dir,
-                     run_in_gui=False, event_handler=None):
-        """Create a command line for linaro-media-create based on the settings
-        provided then run linaro-media-create, either in a separate thread
-        (GUI mode) or in the current one (CLI mode)."""
+    def build_lmc_command(self,
+                          binary_file,
+                          hwpack_file,
+                          settings,
+                          tools_dir,
+                          run_in_gui=False):
 
         import linaro_image_tools.utils
 
-        if event_handler == None:
-            event_handler = self.DummyEventHandler()
-
         args = []
         args.append("pkexec")
 
@@ -100,41 +89,6 @@ 
         if run_in_gui:
             args.append("--nocheck-mmc")
 
-        event_handler.event_start("download OS")
-        try:
-            binary_file = self.download(image_url,
-                                        settings["force_download"],
-                                        show_wx_progress=run_in_gui,
-                                        wx_progress_title=
-                                         "Downloading file 1 of 2")
-        except Exception:
-            # Download error. Hardly matters what, we can't continue.
-            print "Unexpected error:", sys.exc_info()[0]
-            logging.error("Unable to download " + image_url + " - aborting.")
-        event_handler.event_end("download OS")
-
-        if binary_file == None:  # User hit cancel when downloading
-            sys.exit(0)
-
-        event_handler.event_start("download hwpack")
-        try:
-            hwpack_file = self.download(hwpack_url,
-                                        settings["force_download"],
-                                        show_wx_progress=run_in_gui,
-                                        wx_progress_title=
-                                         "Downloading file 2 of 2")
-        except Exception:
-            # Download error. Hardly matters what, we can't continue.
-            print "Unexpected error:", sys.exc_info()[0]
-            logging.error("Unable to download " + hwpack_url + " - aborting.")
-        event_handler.event_end("download hwpack")
-
-        if hwpack_file == None:  # User hit cancel when downloading
-            sys.exit(0)
-
-        logging.info("Have downloaded OS binary to", binary_file,
-                     "and hardware pack to", hwpack_file)
-
         if 'rootfs' in settings and settings['rootfs']:
             args.append("--rootfs")
             args.append(settings['rootfs'])
@@ -157,34 +111,74 @@ 
         args.append("--hwpack")
         args.append(hwpack_file)
 
-        logging.info(args)
-
-        if run_in_gui:
-            self.lmcargs        = args
-            self.event_handler  = event_handler
-            self.started_lmc    = False
-            return
-
-        else:
-            self.create_process = subprocess.Popen(args)
-            self.create_process.wait()
+        logging.info(" ".join(args))
+
+        return args
+
+    def create_media(self, image_url, hwpack_url, settings, tools_dir):
+        """Create a command line for linaro-media-create based on the settings
+        provided then run linaro-media-create, either in a separate thread
+        (GUI mode) or in the current one (CLI mode)."""
+
+        to_download = [(image_url, "OS"),
+                       (hwpack_url, "hwpack")]
+        downloaded_files = self.download_files(to_download,
+                                               settings)
+
+        args = self.build_lmc_command(downloaded_files['OS'],
+                                      downloaded_files['hwpack'],
+                                      settings,
+                                      tools_dir)
+
+        self.create_process = subprocess.Popen(args)
+        self.create_process.wait()
 
     class LinaroMediaCreate(threading.Thread):
         """Thread class for running linaro-media-create"""
-        def __init__(self, event_handler, lmcargs, event_queue):
+        def __init__(self,
+                     image_url,
+                     hwpack_url,
+                     file_handler,
+                     event_queue,
+                     settings,
+                     tools_dir):
+
             threading.Thread.__init__(self)
-            self.event_handler = event_handler
-            self.lmcargs = lmcargs
-            self.event_queue = event_queue
+
+            self.image_url      = image_url
+            self.hwpack_url     = hwpack_url
+            self.file_handler   = file_handler
+            self.event_queue    = event_queue
+            self.settings       = settings
+            self.tools_dir      = tools_dir
 
         def run(self):
-            """Start linaro-media-create and look for lines in the output that:
-            1. Tell us that an event has happened that we can use to update the
-               UI progress.
-            2. Tell us that linaro-media-create is asking a question that needs
-               to be re-directed to the GUI"""
-
-            self.create_process = subprocess.Popen(self.lmcargs,
+            """
+            1. Download required files.
+            2. Build linaro-media-create command
+            3. Start linaro-media-create and look for lines in the output that:
+               1. Tell us that an event has happened that we can use to update
+                  the UI progress.
+               2. Tell us that linaro-media-create is asking a question that
+                  needs to be re-directed to the GUI
+            """
+
+            to_download = [(self.image_url, "OS"),
+                           (self.hwpack_url, "hwpack")]
+
+            downloaded_files = self.file_handler.download_files(
+                                                            to_download,
+                                                            self.settings,
+                                                            self.event_queue)
+
+            lmc_command = self.file_handler.build_lmc_command(
+                                                    downloaded_files['OS'],
+                                                    downloaded_files['hwpack'],
+                                                    self.settings,
+                                                    self.tools_dir,
+                                                    True)
+
+            self.create_process = subprocess.Popen(lmc_command,
                                                    stdin=subprocess.PIPE,
                                                    stdout=subprocess.PIPE,
                                                    stderr=subprocess.STDOUT)
@@ -262,18 +256,6 @@ 
             print >> self.create_process.stdin, text
             self.waiting_for_event_response = False
 
-    def start_lmc_gui_thread(self, event_queue):
-        self.lmc_thread = self.LinaroMediaCreate(self.event_handler,
-                                                 self.lmcargs, event_queue)
-        self.lmc_thread.start()
-
-    def kill_create_media(self):
-        pass  # TODO: Something!
-              # Need to make sure all child processes are terminated.
-
-    def send_to_create_process(self, text):
-        self.lmc_thread.send_to_create_process(text)
-
     def name_and_path_from_url(self, url):
         """Return the file name and the path at which the file will be stored
         on the local system based on the URL we are downloading from"""
@@ -290,34 +272,94 @@ 
 
         return file_name, file_path
 
-    def create_wx_progress(self, title, message):
-        """Create a standard WX progrss dialog"""
-        import wx
-        self.dlg = wx.ProgressDialog(title,
-                                     message,
-                                     maximum=1000,
-                                     parent=None,
-                                     style=wx.PD_CAN_ABORT
-                                      | wx.PD_APP_MODAL
-                                      | wx.PD_ELAPSED_TIME
-                                      | wx.PD_AUTO_HIDE
-                                      | wx.PD_REMAINING_TIME)
-
-    def timer_ping(self):
-        self.update_wx_process(self.download_count)
-
-    def update_wx_progress(self, count):
-        self.download_count = count
-        (self.do_download, skip) = self.dlg.Update(count)
-
-    def download(self, url, force_download=False,
-                 show_wx_progress=False, wx_progress_title=None):
+    def urllib2_open(self, url):
+        maxtries = 10
+        for trycount in range(0, maxtries):
+            try:
+                response = urllib2.urlopen(url)
+            except:
+                if trycount < maxtries - 1:
+                    print "Unable to connect to", url, "retrying in 5 seconds..."
+                    time.sleep(5)
+                    continue
+                else:
+                    print "Connect failed:", url
+                    raise
+                    return None
+            else:
+                return response
+
+        return None
+
+    def download_files(self,
+                       downloads_list,
+                       settings,
+                       event_queue=None):
+        """
+        Download files specified in the downloads_list, which is a list of
+        url, name tuples.
+        """
+
+        downloaded_files = {}
+
+        bytes_to_download = 0
+
+        for url, name in downloads_list:
+            file_name, file_path = self.name_and_path_from_url(url)
+
+            file_name = file_path + os.sep + file_name
+            if os.path.exists(file_name):
+                continue  # If file already exists, don't download it
+
+            response = self.urllib2_open(url)
+            if response:
+                bytes_to_download += int(response.info()
+                                          .getheader('Content-Length').strip())
+                response.close()
+
+        if event_queue:
+            event_queue.put(("update",
+                             "download",
+                             "total bytes",
+                             bytes_to_download))
+
+        for url, name in downloads_list:
+            if event_queue:
+                event_queue.put(("start", "download " + name))
+                event_queue.put(("update",
+                                 "download",
+                                 "name",
+                                 name))
+
+            path = None
+            try:
+                path = self.download(url,
+                                     event_queue,
+                                     settings["force_download"])
+            except Exception:
+                # Download error. Hardly matters what, we can't continue.
+                print "Unexpected error:", sys.exc_info()[0]
+                logging.error("Unable to download " + url + " - aborting.")
+
+            if event_queue:
+                event_queue.put(("end", "download " + name))
+
+            if path == None:  # User hit cancel when downloading
+                sys.exit(0)
+
+            downloaded_files[name] = path
+            logging.info("Have downloaded {0} to {1}".format(name, path))
+
+        return downloaded_files
+
+    def download(self,
+                 url,
+                 event_queue,
+                 force_download=False):
         """Downloads the file requested buy URL to the local cache and returns
         the full path to the downloaded file"""
 
         file_name, file_path = self.name_and_path_from_url(url)
-
-        just_file_name = file_name
         file_name = file_path + os.sep + file_name
 
         if not os.path.isdir(file_path):
@@ -328,23 +370,9 @@ 
                                      "--force-download to override).")
             return file_name
 
-        logging.info("Fetching", url)
+        logging.info("Fetching " + url)
 
-        maxtries = 10
-        for trycount in range(0, maxtries):
-            try:
-                response = urllib2.urlopen(url)
-            except:
-                if trycount < maxtries - 1:
-                    print "Unable to download", url, "retrying in 5 seconds..."
-                    time.sleep(5)
-                    continue
-                else:
-                    print "Download failed for some reason:", url
-                    raise
-                    return
-            else:
-                break
+        response = self.urllib2_open(url)
 
         self.do_download = True
         file_out = open(file_name, 'w')
@@ -356,28 +384,22 @@ 
 
         if show_progress:
             chunk_size = download_size_in_bytes / 1000
-            if show_wx_progress:
-                if wx_progress_title == None:
-                    wx_progress_title = "Downloading File"
-                self.create_wx_progress(wx_progress_title,
-                                        "Downloading " + just_file_name)
-            else:
+            if not event_queue:
                 print "Fetching", url
         else:
             chunk_size = download_size_in_bytes
 
-        if show_progress and show_wx_progress:
-            # Just update the download box before we get the first %
-            self.update_wx_progress(0)
-
         printed_progress = False
         while self.do_download:
             chunk = response.read(chunk_size)
             if len(chunk):
                 # Print a % download complete so we don't get too bored
                 if show_progress:
-                    if show_wx_progress:
-                        self.update_wx_progress(chunks_downloaded)
+                    if event_queue:
+                        event_queue.put(("update",
+                                         "download",
+                                         "progress",
+                                         len(chunk)))
                     else:
                         # Have 1000 chunks so div by 10 to get %...
                         sys.stdout.write("\r%d%%" % (chunks_downloaded / 10))
@@ -400,7 +422,7 @@ 
 
         return file_name
 
-    def download_if_old(self, url, force_download, show_wx_progress=False):
+    def download_if_old(self, url, event_queue, force_download):
         file_name, file_path = self.name_and_path_from_url(url)
 
         file_path_and_name = file_path + os.sep + file_name
@@ -411,25 +433,25 @@ 
             force_download = (force_download == True
                               or (    time.mktime(time.localtime())
                                     - os.path.getmtime(file_path_and_name)
-                                  > 60 * 60 * 24))
+                                  > 60 * 60))
         except OSError:
             force_download = True  # File not found...
 
-        return self.download(url, force_download, show_wx_progress)
+        return self.download(url, event_queue, force_download)
 
     def update_files_from_server(self, force_download=False,
-                                 show_wx_progress=False):
+                                 event_queue=None):
 
-        settings_url     = "http://z.nanosheep.org/fetch_image_settings.yaml"
-        server_index_url = "http://z.nanosheep.org/server_index.bz2"
+        settings_url     = "http://releases.linaro.org/fetch_image/fetch_image_settings.yaml"
+        server_index_url = "http://releases.linaro.org/fetch_image/server_index.bz2"
 
         self.settings_file = self.download_if_old(settings_url,
-                                                  force_download,
-                                                  show_wx_progress)
+                                                  event_queue,
+                                                  force_download)
 
         self.index_file = self.download_if_old(server_index_url,
-                                               force_download,
-                                               show_wx_progress)
+                                               event_queue,
+                                               force_download)
 
         zip_search = re.search(r"^(.*)\.bz2$", self.index_file)
 
@@ -484,9 +506,9 @@ 
             self.settings['UI']['reverse-translate'][value] = key
 
     def parse_args(self, args):
-        parser = argparse.ArgumentParser(description=
-                                         "Create a board image, first "
-                                         "downloading any required files.")
+        parser = argparse.ArgumentParser(description="Create a board image, "
+                                         "first downloading any required "
+                                         "files.")
 
         for (key, value) in self.settings['choice'].items():
             parser.add_argument(
@@ -983,7 +1005,7 @@ 
                 loop_date_increment = -one_day
 
             test_date[in_the] = current_date
-            
+
             while test_date[in_the] <= max_search_date:
                 test_date[in_the] += loop_date_increment