diff mbox

[Branch,~linaro-image-tools/linaro-image-tools/trunk] Rev 373: Merging fetch_image_ui.py to trunk - a GUI that guides the user through the process of identifyin...

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

Commit Message

James Tunnicliffe July 7, 2011, 9:24 a.m. UTC
Merge authors:
  James Tunnicliffe (dooferlad)
------------------------------------------------------------
revno: 373 [merge]
committer: James Tunnicliffe <james.tunnicliffe@linaro.org>
branch nick: linaro-image-tools
timestamp: Thu 2011-07-07 10:22:25 +0100
message:
  Merging fetch_image_ui.py to trunk - a GUI that guides the user through the process of identifying their hardware and what Linaro software they would like to run on it, downloading the required files (hardware packs and OS binaries) and generating a file system using linaro-media-create.
added:
  fetch_image.py
  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.py'
--- fetch_image.py	1970-01-01 00:00:00 +0000
+++ fetch_image.py	2011-06-17 13:10:22 +0000
@@ -0,0 +1,79 @@ 
+#!/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 sys
+import os
+import linaro_image_tools.FetchImage as FetchImage
+import logging
+
+
+def main():
+    file_handler = FetchImage.FileHandler()
+    config = FetchImage.FetchImageConfig()
+
+    # Unfortunately we need to do a bit of a hack here and look for some
+    # options before performing a full options parse.
+    clean_cache = ("--clean-cache" in sys.argv[1:]
+                   or "-x" in sys.argv[1:])
+
+    force_download = ("--force-download" in sys.argv[1:]
+                      or "-d" in sys.argv[1:])
+
+    if clean_cache:
+        file_handler.clean_cache()
+
+    # If the settings file and server index need updating, grab them
+    file_handler.update_files_from_server(force_download)
+
+    # Load settings YAML, which defines the parameters we ask for and
+    # acceptable responses from the user
+    config.read_config(file_handler.settings_file)
+
+    # Using the settings that the YAML defines as what we need for a build,
+    # generate a command line parser and parse the command line
+    config.parse_args(sys.argv[1:])
+
+    if config.args['platform'] == "snapshot":
+        config.args['release_or_snapshot'] = "snapshot"
+    else:
+        config.args['release_or_snapshot'] = "release"
+
+    # Using the config we have, look up URLs to download data from in the
+    # server index
+    db = FetchImage.DB(file_handler.index_file)
+
+    image_url, hwpack_url = db.get_image_and_hwpack_urls(config.args)
+
+    if(image_url and hwpack_url):
+
+        tools_dir = os.path.dirname(__file__)
+        if tools_dir == '':
+            tools_dir = None
+
+        file_handler.create_media(image_url, hwpack_url,
+                                  config.args, tools_dir)
+    else:
+        logging.error(
+            "Unable to find files that match the parameters specified")
+
+if __name__ == '__main__':
+    main()

=== added file 'fetch_image_ui.py'
--- fetch_image_ui.py	1970-01-01 00:00:00 +0000
+++ fetch_image_ui.py	2011-06-17 18:29:14 +0000
@@ -0,0 +1,1620 @@ 
+#!/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_url_lookup(self):
+        self.settings = self.config.settings
+        self.settings['release_or_snapshot'] = "snapshot"
+
+        #--- Test first with a 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))
+            # 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)
+
+        #--- Now test release build lookup ---
+        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()