diff mbox

[Branch,~linaro-image-tools/linaro-image-tools/trunk] Rev 360: Added the first version of fetch_image_ui.py

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

Commit Message

James Tunnicliffe July 6, 2011, 5:56 p.m. UTC
------------------------------------------------------------
revno: 360
committer: James Tunnicliffe <james.tunnicliffe@linaro.org>
branch nick: linaro-image-tools
timestamp: Wed 2011-07-06 18:54:50 +0100
message:
  Added the first version of fetch_image_ui.py
added:
  fetch_image_ui.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

=== added file 'fetch_image_ui.py'
--- fetch_image_ui.py	1970-01-01 00:00:00 +0000
+++ fetch_image_ui.py	2011-07-06 17:54:50 +0000
@@ -0,0 +1,1661 @@ 
+#!/usr/bin/env python
+# Copyright (C) 2010, 2011 Linaro
+#
+# Author: James Tunnicliffe <james.tunnicliffe@linaro.org>
+#
+# This file is part of Linaro Image Tools.
+#
+# Linaro Image Tools 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.
+#
+# Linaro Image Tools 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 Linaro Image Tools; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+# USA.
+
+import wx
+import wx.wizard
+import wx.wizard as wiz
+import sys
+import re
+import os
+import linaro_image_tools.FetchImage as FetchImage
+import string
+import unittest
+import operator
+import Queue
+
+
+def add_button(bind_to,
+               sizer,
+               label,
+               style,
+               select_event,
+               hover_event,
+               unhover_event):
+
+    """Create a radio button with event bindings."""
+    if(style != None):
+        radio_button = wx.RadioButton(bind_to, label=label, style=style)
+    else:
+        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)
+    wx.EVT_ENTER_WINDOW(radio_button, hover_event)
+    wx.EVT_LEAVE_WINDOW(radio_button, unhover_event)
+
+    return radio_button
+
+
+class ReleaseOrSnapshotPage(wiz.PyWizardPage):
+    """Ask the user if they want to use a release or a snapshot"""
+
+    def __init__(self, parent, config):
+        wiz.PyWizardPage.__init__(self, parent)
+        self.config = config
+        self.settings = self.config.settings
+        self.sizer = wx.BoxSizer(wx.VERTICAL)
+        self.next = None
+        self.prev = None
+
+        self.sizer.Add(wx.StaticText(self, -1,
+"""This Wizard will write an operating system of your choosing to
+either a disk image or to an MMC card. First we need to know if
+your priority is stability or the latest and greatest features."""))
+
+        self.box1 = wx.BoxSizer(wx.VERTICAL)
+
+        self.button_text = {'release':  "I would like to run stable, "
+                                        "tested software.",
+                            'snapshot': "I would like to run untested, but "
+                                        "more up-to-date software."}
+
+        add_button(self, self.box1, self.button_text['release'],
+                   wx.RB_GROUP, self.event_radio_button_select, None, None)
+
+        # Save the setting for the default selected value
+        self.settings['release_or_snapshot'] = "release"
+
+        add_button(self, self.box1, self.button_text['snapshot'], None,
+                   self.event_radio_button_select, None, None)
+
+        self.sizer.Add(self.box1, 0, wx.ALIGN_LEFT | wx.ALL, 5)
+
+        self.SetSizerAndFit(self.sizer)
+        self.sizer.Fit(self)
+        self.Move((50, 50))
+
+    def event_radio_button_select(self, event):
+        self.radio_selected = event.GetEventObject().GetLabel()
+        # The radio button can be release, snapshot or "latest snapshot"
+        if(self.radio_selected == self.button_text['release']):
+            self.settings['release_or_snapshot'] = "release"
+        else:
+            self.settings['release_or_snapshot'] = "snapshot"
+
+    def SetNext(self, next):
+        self.next = next
+
+    def GetNext(self):
+        return self.next
+
+
+class AboutMyHardwarePage(wiz.WizardPageSimple):
+    """Ask the user about their hardware. This only asks about the board, not
+       any specific hardware packs because there can be multiple names for the
+       same hardware pack or sometimes a hardware pack is only available in the
+       releases or snapshots repository. We whittle down the choice as we go
+       and the user can chose a hardare pack (if they don't like the default)
+       under advanced options in the Linaro Media Create options
+       page"""
+
+    def __init__(self, parent, config, db, width):
+        wiz.WizardPageSimple.__init__(self, parent)
+        self.settings = config.settings
+        self.db = db
+        self.sizer = wx.BoxSizer(wx.VERTICAL)
+        self.box1 = wx.BoxSizer(wx.VERTICAL)
+        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")
+
+        header.Wrap(width - 10)  # -10 because boarder below is 5 pixels wide
+
+        #--- Hardware Combo Box ---
+        # Make sure that the displayed release is the one set in settings if
+        # no selection is made
+        if "panda" in self.settings['choice']['hardware'].keys():
+            default_hardware = "panda"
+        else:
+            default_hardware = self.settings['choice']['hardware'].keys()[-1]
+
+        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)
+
+        self.Bind(wx.EVT_COMBOBOX,
+                  self.event_combo_box_hardware,
+                  self.cb_hardware)
+        self.box1.Add(self.cb_hardware, 0,
+                      wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5)
+
+        self.sizer.Add(header)
+        self.sizer.Add(self.box1, 0, wx.ALIGN_LEFT | wx.ALL, 5)
+        self.sizer.Add(self.box2, 0, wx.ALIGN_LEFT | wx.ALL, 5)
+        self.SetSizerAndFit(self.sizer)
+        self.sizer.Fit(self)
+        self.Move((50, 50))
+
+    def on_page_changing(self):
+        self.update_hardware_box()
+
+    def update_hardware_box(self):
+        self.cb_hardware.Clear()
+
+        sorted_hardware_names = sorted(self.settings['choice']['hardware']
+                                                                  .iteritems(),
+                                       key=operator.itemgetter(1))
+
+        table = self.settings['release_or_snapshot'] + "_hwpacks"
+
+        for device_name, human_readable_name in sorted_hardware_names:
+            for hwpack in self.settings['choice']['hwpack'][device_name]:
+                if self.db.hardware_is_available_in_table(table, hwpack):
+                    self.cb_hardware.Append(human_readable_name, device_name)
+                    break
+
+    #--- Event(s) ---
+    def event_combo_box_hardware(self, event):
+        self.settings['hardware'] = (event
+                                     .GetEventObject()
+                                      .GetClientData(event.GetSelection())
+                                       .encode('ascii'))
+
+        self.settings['compatable_hwpacks'] = (
+                self.settings['choice']['hwpack'][self.settings['hardware']])
+    #--- END event(s) ---
+
+
+class SelectStableRelease(wiz.WizardPageSimple):
+    """Ask the user which Linaro release they would like to run."""
+    def __init__(self, parent, config, db, width):
+        wiz.WizardPageSimple.__init__(self, parent)
+        self.settings = config.settings
+        self.db = db
+        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.Wrap(width - 10)  # -10 because boarder below is 5 pixels wide
+
+        self.sizer.Add(header)
+        self.box1 = wx.BoxSizer(wx.VERTICAL)
+
+        platforms = []
+        for key, value in self.settings['choice']['platform'].items():
+            platforms.append(key)
+
+        default_release = self.settings['UI']['translate'][platforms[-1]]
+        self.cb_release = wx.ComboBox(self,
+                                      value = default_release,
+                                      style = wx.CB_DROPDOWN | wx.CB_READONLY)
+        self.Bind(wx.EVT_COMBOBOX,
+                  self.event_combo_box_release,
+                  self.cb_release)
+
+        if(default_release in self.settings['UI']['translate']):
+            default_release = self.settings['UI']['translate'][default_release]
+        self.settings['platform'] = (
+                    self.settings['UI']['reverse-translate'][default_release])
+
+        for item in platforms:
+            if(item in self.settings['UI']['translate']):
+                new_item = self.settings['UI']['translate'][item]
+                item = new_item
+
+            self.cb_release.Append(item, item.upper())
+
+        self.cb_build = wx.ComboBox(self,
+                                    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,
+                      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)
+        self.sizer.Fit(self)
+        self.Move((50, 50))
+
+    def update_build_box(self):
+        """Depending on what hardware has been chosen, the OS list may be
+        restricted. Filter out anything that is unavailable."""
+        self.cb_build.Clear()
+
+        builds = self.db.get_builds(self.settings['platform'])
+        self.cb_build.SetValue("No build available")
+
+        for build in builds:
+            if( self.db.hardware_is_available_for_platform_build(
+                                          self.settings['compatable_hwpacks'],
+                                          self.settings['platform'],
+                                          build)
+                and self.db.build_is_available_for_platform_image(
+                                "release_binaries",
+                                self.settings['platform'],
+                                self.settings['image'],
+                                build)):
+
+                self.cb_build.Append(build)
+                self.cb_build.SetValue(build)
+                self.settings['release_build'] = build
+
+        available_hwpacks = (
+            self.db.get_available_hwpacks_for_hardware_build_plaform(
+                                          self.settings['compatable_hwpacks'],
+                                          self.settings['platform'],
+                                          self.settings['release_build']))
+
+        if len(available_hwpacks):
+            self.settings['hwpack'] = available_hwpacks[0]
+            self.wizard.FindWindowById(wx.ID_FORWARD).Enable()
+        else:
+            self.wizard.FindWindowById(wx.ID_FORWARD).Disable()
+
+    def update_release_and_build_boxes(self):
+        """Depending on what hardware has been chosen, some builds may be
+           unavailable..."""
+        self.cb_release.Clear()
+
+        default_release = None
+        for platform, value in self.settings['choice']['platform'].items():
+            if(self.db.hardware_is_available_for_platform(
+                                          self.settings['compatable_hwpacks'],
+                                          platform)
+               and len(self.db.execute_return_list(
+                               'select * from release_binaries '
+                               'where platform == ? and image == ?',
+                                (platform, self.settings['image'])))):
+
+                if(platform in self.settings['UI']['translate']):
+                    platform = self.settings['UI']['translate'][platform]
+
+                self.cb_release.Append(platform, platform.upper())
+                if not default_release or default_release < platform:
+                    default_release = platform
+
+        self.settings['platform'] = (
+                    self.settings['UI']['reverse-translate'][default_release])
+        self.cb_release.SetValue(default_release)
+        self.update_build_box()
+
+    #--- Event(s) ---
+    def event_combo_box_release(self, evt):
+        str = evt.GetString().encode('ascii').lower()
+        if(str in self.settings['UI']['reverse-translate']):
+            str = self.settings['UI']['reverse-translate'][str]
+        self.settings['platform'] = str
+
+        self.update_build_box()
+
+    def event_combo_box_build(self, evt):
+        self.settings['release_build'] = evt.GetString().encode('ascii')
+    #--- END event(s) ---
+
+
+class SelectSnapshot(wiz.WizardPageSimple):
+    """Present the user with a calendar widget and a list of builds available
+    on the selected date so they can chose a snapshot. Filter out days when
+    their chosen hardware does not have an available build."""
+
+    def __init__(self, parent, config, db, width):
+        wiz.WizardPageSimple.__init__(self, parent)
+        self.settings = config.settings
+        self.db = db
+        self.wizard = parent
+        self.width = width
+        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.")
+        header.Wrap(width - 10)  # -10 because boarder below is 5 pixels wide
+
+        box1 = wx.BoxSizer(wx.VERTICAL)
+        self.sizer.Add(header)
+
+        # Set today as the default build date in settings
+        # (matches the date picker)
+        self.today = wx.DateTime()
+        self.today.SetToCurrent()
+        self.settings['build_date'] = self.today.FormatISODate().encode('ascii')
+
+        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 ---
+        # Make sure that the displayed build is the one set in settings if no
+        # selection is made
+        self.settings['build_number'] = 0
+        self.update_build()
+        self.cb_build = wx.ComboBox(self,
+                                    style = wx.CB_DROPDOWN | wx.CB_READONLY)
+        self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_build, self.cb_build)
+
+        #--- Layout ---
+        # -- Combo boxes for hardware and image selection --
+
+        grid2 = wx.FlexGridSizer(0, 2, 0, 0)
+        grid2.Add(dpc, 0, wx.ALIGN_LEFT | wx.ALL, 5)
+        grid2.Add(self.cb_build, 0, wx.ALIGN_LEFT | wx.ALL, 5)
+
+        box1.Add(grid2, 0, wx.ALIGN_LEFT | wx.ALL, 5)
+
+        self.sizer.Add(box1, 0, wx.ALIGN_LEFT | wx.ALL, 5)
+
+        self.help_text = wx.StaticText(self)
+        self.sizer.Add(self.help_text, 1, wx.EXPAND, 5)
+
+        self.SetSizer(self.sizer)
+        self.sizer.Fit(self)
+        self.Move((50, 50))
+
+    def update_platform(self):
+        build_and_date = self.settings['snapshot_build'].split(":")
+
+        if len(build_and_date) == 2:
+            self.settings['platform'] = (
+                    self.db.execute_return_list(
+                            "select platform from snapshot_binaries "
+                            "where date == ? and build == ?",
+                            (build_and_date[0], build_and_date[1])))
+
+            if len(self.settings['platform']) > 0:
+                self.settings['platform'] = self.settings['platform'][0][0]
+
+    def update_build(self):
+        small_date = re.sub('-', '', self.settings['build_date'])
+        self.settings['snapshot_build'] = (small_date
+                                           + ":"
+                                           + str(self.settings['build_number']))
+
+    def fill_build_combo_box_for_date(self, date):
+        """Every time a date is chosen, this function should be called. It will
+        check to see if a compatible build is available. If there isn't, it
+        will search for one and provide some help text to tell the user when
+        compatable builds were built."""
+        # Re-populate the build combo box
+
+        self.cb_build.Clear()
+
+        builds = self.db.get_binary_builds_on_day_from_db(
+                                      self.settings['image'],
+                                      date,
+                                      self.settings['compatable_hwpacks'])
+
+        if len(builds):
+            max = 0
+            for item in builds:
+                #Always get a tuple, only interested in the first entry
+                item = item[0]
+                self.cb_build.Append(item, item.upper())
+
+                if item > max:
+                    max = item
+
+            self.cb_build.SetValue(max)
+            self.wizard.FindWindowById(wx.ID_FORWARD).Enable()
+            self.help_text.SetLabel("")
+
+        else:
+            self.cb_build.SetValue("No builds available")
+            future_date, past_date = self.db.get_next_prev_day_with_builds(
+                                           self.settings['image'],
+                                           date,
+                                           self.settings['compatable_hwpacks'])
+
+            help_text = None
+
+            if future_date and past_date:
+                help_text = ("There are no builds that match your "
+                             "specifications available on the selected date. "
+                             "The previous build was on " + past_date +
+                             " and the next build was on " + future_date + ".")
+            elif future_date:
+                help_text = ("There are no builds that match your "
+                             "specifications available on the selected date. "
+                             "The next build was on " + future_date +
+                             " and I couldn't find a past build (looked one "
+                             "year back from the selected date).")
+            elif past_date:
+                help_text = ("There are no builds that match your "
+                             "specifications available on the selected date. "
+                             "The previous build was on " + past_date)
+                if date != self.today.FormatISODate().encode('ascii'):
+                    help_text += (" and I couldn't find a future build (I "
+                                  "looked up to one year forward from the "
+                                  "selected date).")
+            else:
+                help_text = ("I could not find any builds that match your "
+                             "specifications close to the selected date (I "
+                             "looked forward and back one year from the "
+                             "selected date).")
+
+            self.help_text.SetLabel(help_text)
+            self.help_text.Wrap(self.width - 10)
+            self.wizard.FindWindowById(wx.ID_FORWARD).Disable()
+
+    #--- Event(s) ---
+    def on_date_changed(self, evt):
+        self.settings['build_date'] = evt.GetDate().FormatISODate().encode('ascii')
+        self.fill_build_combo_box_for_date(self.settings['build_date'])
+        self.update_build()
+
+    def event_combo_box_build(self, evt):
+        self.settings['build_number'] = evt.GetString().encode('ascii').lower()
+        self.update_build()
+    #--- END event(s) ---
+
+
+class SelectOS(wiz.WizardPageSimple):
+    """Ask the user which OS they would like to run. Filter out any choices
+    that are unavailable due to previous choices."""
+    def __init__(self, parent, config, db, width):
+        wiz.WizardPageSimple.__init__(self, parent)
+        self.settings = config.settings
+        self.wizard = parent
+        self.db = db
+        self.width = width
+        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.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)
+        self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_os, self.cb_image)
+
+        #--- Layout ---
+        # -- Combo boxes for hardware and image selection --
+        self.box1.Add(self.cb_image, 0, wx.ALIGN_LEFT | wx.ALL, 5)
+
+        self.sizer.Add(self.box1, 0, wx.ALIGN_LEFT | wx.ALL, 5)
+
+        self.help_text = wx.StaticText(self)
+        self.sizer.Add(self.help_text, 1, wx.EXPAND, 5)
+
+        self.SetSizer(self.sizer)
+        self.sizer.Fit(self)
+        self.Move((50, 50))
+
+    def get_human_os_name(self, item):
+        """Given an OS name from the database, return a human name (either
+        translated from the YAML settings, or just prettified) and if it is a
+        LEB OS or not"""
+
+        item = re.sub("linaro-", "", item)  # Remove any linaro- decoration
+
+        human_name = item
+
+        if item in self.settings['UI']['descriptions']:
+            human_name = self.settings['UI']['descriptions'][item]
+        else:
+            # Make human_name look nicer...
+            human_name = string.capwords(item)
+
+        leb_search = re.search("^LEB:\s*(.*)$", human_name)
+
+        if leb_search:
+            return leb_search.group(1), True
+
+        return human_name, False
+
+    def fill_os_list(self):
+        """Filter the list of OS's from the config file based on the users
+        preferences so all choices in the list are valid (i.e. their hardware
+        is supported for the build they have chosen)."""
+
+        # select unique image from snapshot_binaries/release_binaries to
+        # generate list
+        os_list = None
+        if self.settings['release_or_snapshot'] == "release":
+            os_list = self.db.get_os_list_from('release_binaries')
+        else:
+            os_list = self.db.get_os_list_from('snapshot_binaries')
+
+        self.cb_image.Clear()
+
+        printed_tag = None
+        last_name = None
+        current_image_setting_valid = False
+
+        for state in ["LEB", "other"]:
+            for item in os_list:
+                if item == "old":
+                    # Old is a directory that sometimes hangs around,
+                    # but isn't one we want to display
+                    continue
+
+                # Save the original, untouched image name for use later.
+                # We give it a more human name for display
+                original = item
+                item = re.sub("linaro-", "", item)
+
+                os_hardware_combo_available = (
+                            self.db.image_hardware_combo_available(
+                                    self.settings['release_or_snapshot'],
+                                    original,
+                                    self.settings['compatable_hwpacks']))
+
+                if os_hardware_combo_available:
+                    human_name, is_LEB = self.get_human_os_name(item)
+
+                    if item == self.settings['image']:
+                        current_image_setting_valid = True
+
+                    if state == "LEB" and is_LEB:
+
+                        if printed_tag != state:
+                            self.cb_image.Append(
+                                            "- Linaro Supported Releases -")
+                            printed_tag = state
+
+                        self.cb_image.Append(human_name, original)
+
+                        if self.settings['image'] == None:
+                            self.settings['image'] = original
+
+                    elif state != "LEB" and not is_LEB:
+                        if printed_tag != state:
+                            self.cb_image.Append(
+                                            "- Community Supported Releases -")
+                            printed_tag = state
+
+                        self.cb_image.Append(human_name, original)
+
+                    last_name = original
+
+        if(    self.settings['image'] != None
+           and current_image_setting_valid == False):
+            # If we have an image setting, but it doesn't match the OS list, we
+            # have switched OS list. It may be that adding/removing "linaro-"
+            # from the name will get a match.
+
+            if re.search("linaro-", self.settings['image']):
+                test_name = re.sub("linaro-", "", self.settings['image'])
+            else:
+                test_name = "linaro-" + self.settings['image']
+
+            if test_name in os_list:
+                # Success! We have translated the name and can retain the
+                # "old setting"
+                self.settings['image'] = test_name
+                current_image_setting_valid = True
+
+        if(   self.settings['image'] == None
+           or current_image_setting_valid == False):
+            # This should only get hit if there are no LEBs available
+            self.settings['image'] = last_name
+
+        assert self.settings['image']
+
+        # Make sure the visible selected value matches the saved setting
+        self.cb_image.SetValue(
+                            self.get_human_os_name(self.settings['image'])[0])
+
+    #--- Event(s) ---
+    def event_combo_box_os(self, evt):
+        self.settings['image'] = self.cb_image.GetClientData(
+                                                            evt.GetSelection())
+
+        if self.settings['image']:  # Is None for items that aren't an OS
+            self.wizard.FindWindowById(wx.ID_FORWARD).Enable()
+            image = re.sub("linaro-", "", self.settings['image'])
+
+            if image + "::long" in self.settings['UI']['descriptions']:
+                self.help_text.SetLabel(self.settings['UI']
+                                                     ['descriptions']
+                                                     [image + "::long"])
+            else:
+                self.help_text.SetLabel("")
+
+        else:  # Have selected help text
+            self.wizard.FindWindowById(wx.ID_FORWARD).Disable()
+            self.help_text.SetLabel("Please select an operating system to run "
+                                    "on your chosen hardware.")
+
+        self.help_text.Wrap(self.width - 10)
+    #--- END event(s) ---
+
+
+class LMC_settings(wiz.WizardPageSimple):
+    """Present the user with, intially, the choice of writing the file system
+    they are going to have created to a file, or directly to a device. Ask
+    which file/device to write to.
+
+    If writing to a device, the user is asked to tick a box saying that they
+    understand that the device they have chosen will be erased.
+
+    If the user ticks the advanced box, more options are shown."""
+
+    def __init__(self, parent, config, db, width):
+        wiz.WizardPageSimple.__init__(self, parent)
+        self.settings = config.settings
+        self.wizard = parent
+        self.sizer = wx.BoxSizer(wx.VERTICAL)
+        self.yes_use_mmc = False
+        self.db = db
+
+        self.settings['path_selected'] = ""
+
+        header = wx.StaticText(self,
+                               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.")
+        header.Wrap(width - 10)  # -10 because boarder below is 5 pixels wide
+
+        #--- Build some widgets ---
+        #-- Target file system --
+        file_systems = ["ext3", "ext4", "btrfs", "ext2"]
+        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)
+
+        for item in file_systems:
+            cb_rootfs.Append(item, item.upper())
+
+        self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_rootfs, cb_rootfs)
+
+        #-- Image size spinner
+        self.image_size_spinner = wx.SpinCtrl(self, -1, "")
+        self.Bind(wx.EVT_SPINCTRL,
+                  self.event_image_size,
+                  self.image_size_spinner)
+
+        #-- Swap size spinner
+        self.swap_size_spinner = wx.SpinCtrl(self, -1, "")
+        self.Bind(wx.EVT_SPINCTRL,
+                  self.event_swap_size,
+                  self.swap_size_spinner)
+
+        #--- Layout ---
+        self.sizer.Add(header, 0, wx.ALIGN_LEFT | wx.ALL, 5)
+        box1 = wx.BoxSizer(wx.VERTICAL)
+        file_dev_grid = wx.FlexGridSizer(0, 2, 0, 0)
+        box1.Add(file_dev_grid, 0, wx.EXPAND)
+        grid1 = wx.FlexGridSizer(0, 2, 0, 0)
+
+        # self.settings['write_to_file_or_device'] should match the first
+        # button below...
+        self.settings['write_to_file_or_device'] = "file"
+        add_button(self,
+                   file_dev_grid,
+                   "Write to file",
+                   wx.RB_GROUP,
+                   self.event_radio_button_select,
+                   None, None)
+
+        add_button(self,
+                   file_dev_grid,
+                   "Write to device",
+                   None,
+                   self.event_radio_button_select,
+                   None, None)
+
+        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']])
+        self.help_text.Wrap(width - 10)
+
+        #-- File/dev picker --
+        file_browse_button = wx.Button(self, -1, "Browse")
+        file_browse_grid   = wx.FlexGridSizer(0, 2, 0, 0)
+        self.file_path_and_name = wx.TextCtrl(self, -1, "", size=(300, -1))
+
+        file_browse_grid.Add(self.file_path_and_name, 0, wx.EXPAND)
+        file_browse_grid.Add(file_browse_button, 0, wx.EXPAND)
+
+        self.Bind(wx.EVT_BUTTON,
+                  self.event_open_file_control,
+                  file_browse_button)
+
+        self.Bind(wx.EVT_TEXT,
+                  self.event_file_path_and_name,
+                  self.file_path_and_name)
+
+        box1.Add(self.help_text, 0, wx.ALIGN_LEFT | wx.ALL, 5)
+
+        box1.Add(file_browse_grid, 0, wx.EXPAND)
+
+        cb1 = wx.CheckBox(self, -1, "Show advanced options")
+        self.Bind(wx.EVT_CHECKBOX, self.event_show_advanced_options, cb1)
+        box1.Add(cb1)
+
+        #-- Combo boxes for hardware and image selection --
+        optional_settings_box_title = wx.StaticBox(
+                                                self,
+                                                label = " Optional Settings ")
+
+        self.optional_settings_box = wx.StaticBoxSizer(
+                                                optional_settings_box_title,
+                                                wx.VERTICAL)
+
+        self.box2 = wx.BoxSizer(wx.VERTICAL)
+
+        self.box2.AddWindow(self.optional_settings_box,
+                            0,
+                            border=2,
+                            flag=wx.ALL | wx.EXPAND)
+
+        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"),
+                                0,
+                                wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
+                                5)
+
+        # We want to sub-devide the cell, to add another grid sizer...
+        file_size_grid = wx.FlexGridSizer(0, 2, 0, 0)
+
+        grid1.Add(file_size_grid,
+                  0,
+                  wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP)
+
+        # Add a spinner that allows us to type/click a numerical value (defined above)
+        file_size_grid.Add(self.image_size_spinner,
+                           0,
+                           wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
+                           5)
+
+        # 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)
+        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"),
+                            0,
+                            wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
+                            5)
+
+        # The swap size (MB only)
+        grid1.Add(self.swap_size_spinner,
+                  0,
+                  wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
+                  5)
+
+        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)
+
+        self.Bind(wx.EVT_COMBOBOX,
+                  self.event_combo_box_hwpack,
+                  self.cb_hwpacks)
+
+        grid1.Add(self.cb_hwpacks,
+                  0,
+                  wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
+                  5)
+
+        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? ")
+
+        self.confirm_mmc_usage_box = wx.StaticBoxSizer(confirm_mmc_usage_title,
+                                                       wx.VERTICAL)
+        cb2 = wx.CheckBox(
+                        self,
+                        -1,
+                        "Yes, erase and use the device I have selected above.")
+
+        self.Bind(wx.EVT_CHECKBOX, self.event_use_mmc_tickbox, cb2)
+        self.confirm_mmc_usage_box.Add(cb2)
+
+        self.box3 = wx.BoxSizer(wx.VERTICAL)
+        self.box3.AddWindow(self.confirm_mmc_usage_box,
+                            0,
+                            border=2,
+                            flag=wx.ALL | wx.EXPAND)
+
+        self.sizer.Add(box1, 0, wx.ALIGN_LEFT | wx.ALL, 0)
+        self.sizer.Add(self.box2, 0, wx.ALIGN_LEFT | wx.ALL, 0)
+        self.sizer.Add(self.box3, 0, wx.ALIGN_LEFT | wx.ALL, 0)
+        self.SetSizer(self.sizer)
+        self.sizer.Fit(self)
+        self.Move((50, 50))
+
+    def on_activate(self):
+        self.update_forward_active_and_mmc_confirm_box_visible()
+        self.set_hwpacks_for_hardware()
+
+    def set_hwpacks_for_hardware(self):
+        self.cb_hwpacks.Clear()
+
+        if self.settings['release_or_snapshot'] == "snapshot":
+            self.settings['build'] = self.settings['snapshot_build']
+
+            date_and_build = self.settings['build'].split(":")
+
+            compatable_hwpacks = (
+                self.db.get_available_hwpacks_for_hardware_snapshot_build(
+                                        self.settings['compatable_hwpacks'],
+                                        self.settings['platform'],
+                                        date_and_build[0],
+                                        date_and_build[1]))
+        else:
+            self.settings['build'] = self.settings['release_build']
+            compatable_hwpacks = (
+                self.db.get_available_hwpacks_for_hardware_build_plaform(
+                                        self.settings['compatable_hwpacks'],
+                                        self.settings['platform'],
+                                        self.settings['build']))
+
+        for hwpack in compatable_hwpacks:
+            self.cb_hwpacks.Append(hwpack)
+
+        self.cb_hwpacks.SetStringSelection(compatable_hwpacks[0])
+        self.settings['hwpack'] = compatable_hwpacks[0]
+
+    def update_forward_active_and_mmc_confirm_box_visible(self):
+        if(    self.settings['path_selected']
+           and self.settings['path_selected'] != ""):
+
+            if (   self.settings['write_to_file_or_device'] == "file"
+                or self.settings['write_to_file_or_device'] == "device"
+                   and self.yes_use_mmc):
+                self.wizard.FindWindowById(wx.ID_FORWARD).Enable()
+            else:
+                self.wizard.FindWindowById(wx.ID_FORWARD).Disable()
+        else:
+            self.wizard.FindWindowById(wx.ID_FORWARD).Disable()
+
+        if self.settings['write_to_file_or_device'] == "device":
+            self.box3.Show(self.confirm_mmc_usage_box, True)
+        else:
+            self.box3.Hide(self.confirm_mmc_usage_box, True)
+
+    # --- Event Handlers ---
+    def event_open_file_control(self, event):
+        if self.settings['write_to_file_or_device'] == "file":
+
+            dlg = wx.FileDialog(self,
+                                message="Save file as ...",
+                                defaultDir=os.getcwd(),
+                                defaultFile="",
+                                style=wx.SAVE)
+
+        elif self.settings['write_to_file_or_device'] == "device":
+            dlg = wx.FileDialog(self,
+                                message="Choose a device",
+                                defaultDir=os.getcwd(),
+                                defaultFile="",
+                                style=wx.OPEN | wx.CHANGE_DIR)
+
+        if dlg.ShowModal() == wx.ID_OK:
+            self.settings['path_selected'] = dlg.GetPaths()[0]
+            self.file_path_and_name.SetValue(self.settings['path_selected'])
+
+        dlg.Destroy()
+        self.update_forward_active_and_mmc_confirm_box_visible()
+
+    def event_file_path_and_name(self, event):
+        self.settings['path_selected'] = event.GetString()
+        self.update_forward_active_and_mmc_confirm_box_visible()
+
+    def event_combo_box_hwpack(self, event):
+        self.settings['hwpack'] = event.GetString().encode('ascii')
+
+    def event_combo_box_rootfs(self, evt):
+        self.settings['rootfs'] = evt.GetString().encode('ascii').lower()
+
+    def event_radio_button_select(self, event):
+        """Search the label of the button that has been selected to work out
+        what we are writing to."""
+        setting_search = re.search(
+                            "write to (\w+)",
+                            event
+                             .GetEventObject()
+                              .GetLabel()
+                               .encode('ascii')
+                                .lower())
+
+        assert setting_search
+
+        self.settings['write_to_file_or_device'] = setting_search.group(1)
+
+        self.help_text.SetLabel(
+               self.help_text_values[self.settings['write_to_file_or_device']])
+
+        self.update_forward_active_and_mmc_confirm_box_visible()
+
+    def event_show_advanced_options(self, event):
+        if event.IsChecked():
+            self.box2.Show(self.optional_settings_box, True)
+        else:
+            self.box2.Hide(self.optional_settings_box, True)
+
+    def event_pick_file_path(self, evt):
+        self.settings['path_selected'] = os.path.abspath(evt.GetPath())
+        self.update_forward_active_and_mmc_confirm_box_visible()
+
+    def update_image_size_setting(self):
+        if(self.image_size_spinner.GetValue() > 0):
+            self.settings['image_size'] = (str(self.image_size_spinner
+                                                                 .GetValue())
+                                           + self.size_unit[0])
+        else:
+            self.settings['image_size'] = None
+
+    def event_image_size(self, event):
+        self.update_image_size_setting()
+
+    def event_chose_unit(self, event):
+        self.size_unit = event.GetString()
+        self.update_image_size_setting()
+
+    def event_swap_size(self, event):
+        self.settings['swap_file'] = str(self.image_size_spinner.GetValue())
+
+    def event_use_mmc_tickbox(self, event):
+        self.yes_use_mmc = event.IsChecked()
+        self.update_forward_active_and_mmc_confirm_box_visible()
+
+
+class RunLMC(wiz.WizardPageSimple):
+    """Present the user with some information about their choices and a button
+    to start linaro-media-create. The linaro-media-create process is started in
+    a new thread and important events are communicated back to the UI through a
+    queue."""
+
+    def __init__(self, parent, config, db, width):
+        wiz.WizardPageSimple.__init__(self, parent)
+        self.settings = config.settings
+        self.sizer = wx.BoxSizer(wx.VERTICAL)
+        self.db = db
+        self.width = width
+        self.wizard = parent
+
+        header = wx.StaticText(self, label = """Installing...""")
+        header.Wrap(width - 10)  # -10 because boarder below is 5 pixels wide
+
+        self.sizer.Add(header)
+        self.box1 = wx.BoxSizer(wx.VERTICAL)
+
+        # 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.Wrap(width - 10)
+
+        self.box1.Add(self.settings_summary_text, 0, wx.ALIGN_LEFT | wx.ALL, 5)
+
+        self.start_button = wx.Button(self, 10, "Start", (20, 20))
+        self.Bind(wx.EVT_BUTTON, self.start_lmc, self.start_button)
+
+        self.start_button.SetToolTipString("Start creating an image, using the"
+                                           "above settings.")
+
+        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.status_grid.Add(wx.StaticText(self, label="Downloading files"),
+                             0,
+                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
+                             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(wx.StaticText(self, label="Unpacking downloads"),
+                             0,
+                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
+                             5)
+
+        self.unpacking_files_status = wx.StaticText(self, label="")
+
+        self.status_grid.Add(self.unpacking_files_status,
+                             0,
+                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
+                             5)
+
+        self.status_grid.Add(wx.StaticText(self, label="Installing packages"),
+                             0,
+                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
+                             5)
+
+        self.installing_packages_status = wx.StaticText(self, label="")
+
+        self.status_grid.Add(self.installing_packages_status,
+                             0,
+                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
+                             5)
+
+        self.status_grid.Add(wx.StaticText(self, label="Create file system"),
+                             0,
+                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
+                             5)
+
+        self.create_file_system_status = wx.StaticText(self, label="")
+
+        self.status_grid.Add(self.create_file_system_status,
+                             0,
+                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
+                             5)
+
+        self.status_grid.Add(wx.StaticText(self, label="Populate file system"),
+                             0,
+                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
+                             5)
+
+        self.populate_file_system_status = wx.StaticText(self, label="")
+
+        self.status_grid.Add(self.populate_file_system_status,
+                             0,
+                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
+                             5)
+
+        self.sizer.Add(self.box1, 0, wx.ALIGN_LEFT | wx.ALL, 5)
+        self.sizer.Add(self.status_grid, 0, wx.ALIGN_LEFT | wx.ALL, 5)
+        self.SetSizerAndFit(self.sizer)
+        self.sizer.Fit(self)
+        self.Move((50, 50))
+
+    def on_activate(self):
+        """Called just before the page is displayed to update the text based on
+        the users preferences."""
+
+        # The build is stored in different forms depending on if we are using a
+        # release or snapshot but from here on in it is a common value
+        if self.settings['release_or_snapshot'] == "snapshot":
+            self.settings['build'] = self.settings['snapshot_build']
+        else:
+            self.settings['build'] = self.settings['release_build']
+
+        settings_summary = ("Press start to create an image with the "
+                            "following settings:\n")
+        settings_summary += "Operating System: " + self.settings['image'] + "\n"
+        settings_summary += "Hardware: " + self.settings['hardware'] + "\n"
+
+        # Assumption is that a file may be in a long path, we don't know how
+        # big the font is and we don't want to allow the path to run off the
+        # end of the line, so if a file is chosen, just show the file name.
+        # Devices are (probably) /dev/some_short_name and the user really needs
+        # to check them, so we show the whole thing.
+        path = self.settings['path_selected']
+        if self.settings['write_to_file_or_device'] == "file":
+            path = self.settings['path_selected'].split(os.sep)[-1]
+
+        settings_summary += (  "Writing image to "
+                             + self.settings['write_to_file_or_device']
+                             + " " 
+                             + path)
+
+        self.settings_summary_text.SetLabel(settings_summary)
+        self.settings_summary_text.Wrap(self.width - 10)
+
+    def start_lmc(self, event):
+        """Start a thread that runs linaro-media-create and a timer, which
+        checks for UI updates every 100ms"""
+
+        if self.settings['write_to_file_or_device'] == "file":
+            self.settings['image_file'] = self.settings['path_selected']
+        elif self.settings['write_to_file_or_device'] == "device":
+            self.settings['mmc'] = self.settings['path_selected']
+        else:
+            assert False, ("self.config.settings['write_to_file_or_device'] "
+                           "was an unexpected value"
+                           + self.settings['write_to_file_or_device'])
+
+        image_url, hwpack_url = self.db.get_image_and_hwpack_urls(self.settings)
+
+        # Currently the UI is blocked when LMC is running, so grey out the
+        # buttons to indicate to the user that they won't work!
+        self.wizard.FindWindowById(wx.ID_BACKWARD).Disable()
+        self.wizard.FindWindowById(wx.ID_CANCEL).Disable()
+
+        if(image_url and hwpack_url):
+
+            print image_url
+            print hwpack_url
+
+            self.file_handler = FetchImage.FileHandler()
+
+            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)
+        else:
+            print >> sys.stderr, ("Unable to find files that match the"
+                                  "parameters specified")
+
+    def timer_ping(self, event):
+        """During start_lmc a timer is started to poll for events from
+        linaro-media-create every 100ms. This is the function which is called
+        to do that polling."""
+
+        if self.event_queue.empty() == False:
+            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")
+
+            else:
+                print >> sys.stderr, "timer_ping: Unhandled event", event
+
+        else:
+            self.timer.Start(milliseconds=100, oneShot=True)
+
+    def unsigned_packages_query(self, package_list):
+        message = ('In order to continue, I need to install some unsigned'
+                   'packages into the image. Is this OK? The packages are:'
+                   '\n\n' + package_list)
+
+        dlg = wx.MessageDialog(self,
+                               message,
+                               'Install Unsigned Packages Into Image?',
+                               wx.YES_NO | wx.NO_DEFAULT)
+
+        choice = dlg.ShowModal()
+        dlg.Destroy()
+
+        return choice == wx.ID_YES
+
+    #--- Event(s) ---
+    def event_start(self, event):
+        if event == "download OS":
+            self.downloading_files_status.SetLabel("Downloading OS")
+        elif event == "download hwpack":
+            self.downloading_files_status.SetLabel("Downloading Hardware Pack")
+        elif event == "unpack":
+            self.unpacking_files_status.SetLabel("Running")
+        elif event == "installing packages":
+            self.installing_packages_status.SetLabel("Running")
+
+        elif re.search('^unverified_packages:', event):
+            # Get rid of event ID and whitespace invariance
+            packages = " ".join(event.split()[1:])
+            install_unsigned_packages = self.unsigned_packages_query(packages)
+
+            if install_unsigned_packages == False:
+                self.file_handler.kill_create_media()
+                sys.exit(1)
+            else:
+                self.file_handler.send_to_create_process("y")
+
+        elif event == "create file system":
+            self.create_file_system_status.SetLabel("Running")
+        elif event == "populate file system":
+            self.populate_file_system_status.SetLabel("Running")
+        else:
+            print "Unhandled start event:", event
+
+    def event_end(self, event):
+        if event == "download OS":
+            self.downloading_files_status.SetLabel("Done (1/2)")
+        elif event == "download hwpack":
+            self.downloading_files_status.SetLabel("Done")
+        elif event == "unpack":
+            self.unpacking_files_status.SetLabel("Done")
+        elif event == "installing packages":
+            self.installing_packages_status.SetLabel("Done")
+        elif event == "create file system":
+            self.create_file_system_status.SetLabel("Done")
+        elif event == "populate file system":
+            self.populate_file_system_status.SetLabel("Done")
+        else:
+            print "Unhhandled end event:", event
+
+    def event_combo_box_release(self, evt):
+        pass
+
+    def event_combo_box_build(self, evt):
+        pass
+    #--- END event(s) ---
+
+
+class TestDriveWizard(wx.wizard.Wizard):
+    def __init__(self, title):
+        wx.wizard.Wizard.__init__(self, None, -1, title, wx.NullBitmap)
+        self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.on_page_changing)
+        self.done_startup = False
+
+    def on_page_changing(self, evt):
+        'Executed before the page changes.'
+
+        if self.done_startup == False:
+            self.pages['lmc_settings'].box2.Hide(
+                            self.pages['lmc_settings'].optional_settings_box,
+                            True)
+
+            self.pages['lmc_settings'].box3.Hide(
+                            self.pages['lmc_settings'].confirm_mmc_usage_box,
+                            True)
+
+            self.done_startup = True
+
+        page = evt.GetPage()
+
+        if evt.GetDirection():  # If going forwards...
+            # Always enable back button if going forwards
+            self.wizard.FindWindowById(wx.ID_BACKWARD).Enable()
+
+            # If going from a select snapshot or select release page, record
+            # which we were on so the back button of the next page works
+            if(self.config.settings['release_or_snapshot'] == "release"):
+                self.pages['select_os'].SetNext(self.pages['select_release'])
+                self.pages['select_release'].SetPrev(self.pages['select_os'])
+
+                self.pages['select_release'].SetNext(self.pages['lmc_settings'])
+                self.pages['lmc_settings'].SetPrev(self.pages['select_release'])
+            else:
+                self.pages['select_os'].SetNext(self.pages['select_snapshot'])
+                self.pages['select_snapshot'].SetPrev(self.pages['select_os'])
+
+                if(page == self.pages['select_os']):
+                    self.pages['select_snapshot'].fill_build_combo_box_for_date(
+                                            self.config.settings['build_date'])
+
+                self.pages['select_snapshot'].SetNext(self.pages['lmc_settings'])
+                self.pages['lmc_settings'].SetPrev(self.pages['select_snapshot'])
+
+            if page == self.pages['hardware_details']:
+                self.pages['select_os'].fill_os_list()
+
+            if page == self.pages['release_or_snapshot']:
+                self.pages['hardware_details'].on_page_changing()
+
+            # If about to move into the release selection, make sure the list
+            # is populated only with releases that are valid with our current
+            # selection
+            if(    page == self.pages['select_os']
+               and self.config.settings['release_or_snapshot'] == "release"):
+                self.pages['select_release'].update_release_and_build_boxes()
+
+            if page == self.pages['select_snapshot']:
+                # Execute when exiting page
+                self.pages['select_snapshot'].update_platform()
+
+            if(   page == self.pages['select_snapshot']
+               or page == self.pages['select_release']):
+                self.pages['lmc_settings'].on_activate()
+
+            if page == self.pages['lmc_settings']:
+                # Forward stays disabled until LMC has finished running
+                self.wizard.FindWindowById(wx.ID_FORWARD).Disable()
+                self.pages['run_lmc'].on_activate()
+
+        else:  # Always enable the forward button if reversing into a page
+            self.wizard.FindWindowById(wx.ID_FORWARD).Enable()
+
+    def go(self, first_page):
+        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)
+
+        # Load settings YAML, which defines the parameters we ask for and
+        # acceptable responses from the user
+        self.config.read_config(file_handler.settings_file)
+
+        # Using the config we have, look up URLs to download data from in
+        # the server index
+        db = FetchImage.DB(file_handler.index_file)
+
+        # Create the wizard and the pages
+        self.wizard = wiz.Wizard(self, -1, "Linaro Media Builder")
+
+        self.pages = {}
+        self.pages['release_or_snapshot'] = ReleaseOrSnapshotPage(self.wizard,
+                                                                  self.config)
+        self.wizard.FitToPage(self.pages['release_or_snapshot'])
+        (width, height) = self.wizard.GetSize()
+
+        self.pages['hardware_details']  = AboutMyHardwarePage(self.wizard,
+                                                              self.config,
+                                                              db,
+                                                              width)
+
+        self.pages['select_release']    = SelectStableRelease(self.wizard,
+                                                              self.config,
+                                                              db,
+                                                              width)
+
+        self.pages['select_snapshot']   = SelectSnapshot(self.wizard,
+                                                         self.config,
+                                                         db,
+                                                         width)
+
+        self.pages['select_os']         = SelectOS(self.wizard,
+                                                   self.config,
+                                                   db,
+                                                   width)
+
+        self.pages['lmc_settings']      = LMC_settings(self.wizard,
+                                                       self.config,
+                                                       db,
+                                                       width)
+
+        self.pages['run_lmc']           = RunLMC(self.wizard,
+                                                 self.config,
+                                                 db,
+                                                 width)
+
+        self.pages['release_or_snapshot'].SetNext(
+                                            self.pages['hardware_details'])
+
+        self.pages['hardware_details'].SetPrev(
+                                            self.pages['release_or_snapshot'])
+
+        self.pages['hardware_details'].SetNext(self.pages['select_os'])
+        self.pages['select_os'].SetPrev(self.pages['hardware_details'])
+        # Select OS goes to select build, which is customised for
+        # releases or snapshots
+        self.pages['lmc_settings'].SetNext(self.pages['run_lmc'])
+        self.pages['run_lmc'].SetPrev(self.pages['lmc_settings'])
+
+        for (name, page) in self.pages.items():
+            self.wizard.GetPageAreaSizer().Add(page)
+
+        self.wizard.RunWizard(self.pages['release_or_snapshot'])
+
+
+def run(start_page = None):
+    """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)
+    w = TestDriveWizard('Simple Wizard')
+    return w.go(start_page)
+
+
+class TestURLLookupFunctions(unittest.TestCase):
+
+    def setUp(self):
+        self.file_handler   = FetchImage.FileHandler()
+        self.file_handler.update_files_from_server(show_wx_progress = True)
+        self.config         = FetchImage.FetchImageConfig()
+        self.config.settings["force_download"] = False
+
+        # Load settings YAML, which defines the parameters we ask for and
+        # acceptable responses from the user
+        self.config.read_config(self.file_handler.settings_file)
+
+        # Using the config we have, look up URLs to download data from in the
+        # server index
+        self.db = FetchImage.DB(self.file_handler.index_file)
+
+    def test_000_url_lookup_snapshot_builds(self):
+        self.settings = self.config.settings
+        self.settings['release_or_snapshot'] = "snapshot"
+
+        #--- Test finding builds near a particular day ---
+        # This functionality is required for further tests, hence forcing the
+        # run order by putting numbers in the function name.
+        today = wx.DateTime()
+        today.SetToCurrent()
+
+        # -- Don't iterate through platforms for snapshot --
+
+        # -- Select hardware --
+        for self.settings['hardware'] in (
+                                   self.settings['choice']['hardware'].keys()):
+
+            compatable_hwpacks = self.settings['choice']['hwpack'][
+                                                    self.settings['hardware']]
+
+            future_date, past_date = self.db.get_next_prev_day_with_builds(
+                                        "linaro-alip",
+                                        today.FormatISODate().encode('ascii'),
+                                        compatable_hwpacks)
+
+            if past_date == None:
+                # Some hardware packs are not available in the snapshot repo,
+                # so just skip if they aren't
+                continue
+
+            builds = self.db.get_binary_builds_on_day_from_db(
+                                                        "linaro-alip",
+                                                        past_date,
+                                                        compatable_hwpacks)
+
+            self.assertTrue(len(builds))
+            # If the above assert fails, either the DB is empty, or
+            # db.get_binary_builds_on_day_from_db failed
+
+    def test_100_url_lookup_snapshots(self):
+        self.settings = self.config.settings
+        self.settings['release_or_snapshot'] = "snapshot"
+
+        #--- Test snapshot build lookup ---
+        # -- Fix a build date --
+        # We only need to look up a single snapshot date. Start with today and
+        # go with the day in the DB, build 0
+        today = wx.DateTime()
+        today.SetToCurrent()
+
+        # -- Don't iterate through platforms for snapshot --
+
+        # -- Select hardware --
+        for self.settings['hardware'] in (
+                                   self.settings['choice']['hardware'].keys()):
+
+            compatable_hwpacks = self.settings['choice']['hwpack'][
+                                                    self.settings['hardware']]
+
+            future_date, past_date = self.db.get_next_prev_day_with_builds(
+                                        "linaro-alip",
+                                        today.FormatISODate().encode('ascii'),
+                                        compatable_hwpacks)
+
+            if past_date == None:
+                # Some hardware packs are not available in the snapshot repo,
+                # so just skip if they aren't
+                continue
+
+            builds = self.db.get_binary_builds_on_day_from_db(
+                                                        "linaro-alip",
+                                                        past_date,
+                                                        compatable_hwpacks)
+
+            self.assertTrue(len(builds))
+            # The above code is tested in test_000_url_lookup_snapshot_build.
+            # If the above assert fails, either the DB is empty, or
+            # db.get_binary_builds_on_day_from_db failed.
+
+            small_date = re.sub('-', '', past_date)
+            self.settings['build'] = small_date + ":" + "0"
+
+            # -- Iterate through hardware packs --
+            for self.settings['hwpack'] in compatable_hwpacks:
+
+                # If hardware pack is available...
+                if(self.settings['hwpack']
+                    in self.db.get_hwpacks('snapshot_hwpacks')):
+
+                    # -- Iterate through images
+                    os_list = self.db.get_os_list_from('snapshot_binaries')
+
+                    for self.settings['image'] in os_list:
+                        if re.search('old', self.settings['image']):
+                            # Directories with old in the name are of no
+                            # interest to us
+                            continue
+
+                        # -- Check build which matches these parameters
+                        #    (builds that don't match are excluded in UI) --
+                        if(    len(self.db.execute_return_list(
+                                    'select * from snapshot_hwpacks '
+                                    'where hardware == ? '
+                                    'and date == ? '
+                                    'and build == ?',
+                                    (self.settings['hwpack'],
+                                     small_date,
+                                     "0")))
+                           and len(self.db.execute_return_list(
+                                    'select * from snapshot_binaries '
+                                    'where image == ? '
+                                    'and date == ? '
+                                    'and build == ?',
+                                    (self.settings['image'],
+                                     small_date,
+                                     "0")))):
+
+                            # - Run the function under test! -
+                            image_url, hwpack_url = (
+                              self.db.get_image_and_hwpack_urls(self.settings))
+
+                            self.assertTrue(image_url)
+                            self.assertTrue(hwpack_url)
+
+    def test_101_url_lookup_releases(self):
+        #--- Test release build lookup ---
+        self.settings = self.config.settings
+        self.settings['release_or_snapshot'] = "release"
+        # -- Select hardware --
+        for self.settings['hardware'] in (
+                                    self.settings['choice']['hardware'].keys()):
+            compatable_hwpacks = (
+                  self.settings['choice']['hwpack'][self.settings['hardware']])
+
+            # -- Iterate through hardware packs --
+            for self.settings['hwpack'] in compatable_hwpacks:
+
+                # If hardware pack is available...
+                if(self.settings['hwpack']
+                    in self.db.get_hwpacks('release_hwpacks')):
+
+                    # -- Iterate through images
+                    os_list = self.db.get_os_list_from('release_binaries')
+
+                    for self.settings['image'] in os_list:
+                        if re.search('old', self.settings['image']):
+                            # Directories with old in the name are of no
+                            # interest to us
+                            continue
+
+                        for platform, ignore in (
+                                  self.settings['choice']['platform'].items()):
+                            self.settings['platform'] = platform
+
+                            # -- Iterate through available builds --
+                            builds = self.db.get_builds(
+                                                    self.settings['platform'],
+                                                    self.settings['image'])
+
+                            for build in builds:
+                                self.settings['build'] = build
+
+                                # -- Check build which matches these parameters
+                                #(builds that don't match are excluded in UI)--
+                                if(    len(self.db.execute_return_list(
+                                            'select * from release_hwpacks '
+                                            'where platform == ? '
+                                            'and hardware == ? '
+                                            'and build == ?',
+                                            (self.settings['platform'],
+                                             self.settings['hwpack'],
+                                             self.settings['build'])))
+                                   and len(self.db.execute_return_list(
+                                            'select * from release_binaries '
+                                            'where platform == ? '
+                                            'and image == ? '
+                                            'and build == ?',
+                                            (self.settings['platform'],
+                                             self.settings['image'],
+                                             self.settings['build'])))):
+
+                                    # - Run the function under test! -
+                                    image_url, hwpack_url = (
+                                        self.db.get_image_and_hwpack_urls(self.settings))
+                                    self.assertTrue(image_url)
+                                    self.assertTrue(hwpack_url)
+
+if __name__ == '__main__':
+    run()