Merge lp:~dooferlad/linaro-image-tools/fetch_image_gui into lp:linaro-image-tools/11.11

Proposed by James Tunnicliffe
Status: Rejected
Rejected by: Loïc Minier
Proposed branch: lp:~dooferlad/linaro-image-tools/fetch_image_gui
Merge into: lp:linaro-image-tools/11.11
Prerequisite: lp:~dooferlad/linaro-image-tools/fetch_image_server_indexer
Diff against target: 1749 lines (+1740/-0)
2 files modified
fetch_image.py (+79/-0)
fetch_image_ui.py (+1661/-0)
To merge this branch: bzr merge lp:~dooferlad/linaro-image-tools/fetch_image_gui
Reviewer Review Type Date Requested Status
James Westby (community) Approve
Review via email: mp+66917@code.launchpad.net

This proposal supersedes a proposal from 2011-06-17.

Description of the change

Initial merge request of fetch_image_ui.py - a GUI with similar functionality to fetch_image.py.

The GUI guides the user through the process of creating an image for their ARM hardware. Files are pulled as required from release.linaro.org and snapshots.linaro.org and combined using linaro-media-create.

--

Now with split up unit tests!

To post a comment you must log in.
Revision history for this message
James Westby (james-w) wrote : Posted in a previous version of this proposal

133 + if(style != None):
134 + radio_button = wx.RadioButton(bind_to, label = label, style = style)
135 + else:
136 + radio_button = wx.RadioButton(bind_to, label = label)

I think that's unnecessary isn't it? style=None is usually the default.

(also we usually avoid spaces around = in method calls)

226 + if "panda" in self.settings['choice']['hardware'].keys():
227 + default_hardware = "panda"

Was this just to make your testing easier? I don't think that the choice should
have a default until we're in a position to remember their last answer.

1562 + def test_url_lookup(self):

Please break up this test in to several tests. These sort of test cases
are a nightmare to debug. Ideally each test method should assert a single
thing.

Thanks,

James

Revision history for this message
James Westby (james-w) : Posted in a previous version of this proposal
review: Needs Fixing
Revision history for this message
James Tunnicliffe (dooferlad) wrote : Posted in a previous version of this proposal

On 24 June 2011 21:58, James Westby <email address hidden> wrote:
> 133     +    if(style != None):
> 134     +        radio_button = wx.RadioButton(bind_to, label = label, style = style)
> 135     +    else:
> 136     +        radio_button = wx.RadioButton(bind_to, label = label)
>
> I think that's unnecessary isn't it? style=None is usually the default.

In this case, sadly not.

> (also we usually avoid spaces around = in method calls)

Noted and fixed.

> 226     +        if "panda" in self.settings['choice']['hardware'].keys():
> 227     +            default_hardware = "panda"
>
> Was this just to make your testing easier? I don't think that the choice should
> have a default until we're in a position to remember their last answer.

Mostly yes. I am sure we could have nothing selected and the next
button disabled by default. I didn't think it was too cheeky to do
since there are a lot of panda boards in the hands of Linaro engineers
:-) I am sure in the future we could remember some choices between
runs as well, which would be useful for just getting the latest build
for your board.

> 1562    +    def test_url_lookup(self):
>
> Please break up this test in to several tests. These sort of test cases
> are a nightmare to debug. Ideally each test method should assert a single
> thing.

I have split it into three, with functions named such that if test x
requires test y to pass before it will pass, x will run first.

--
James Tunnicliffe

Revision history for this message
James Westby (james-w) :
review: Approve
Revision history for this message
Loïc Minier (lool) wrote :

This is marked as approved but not merged, but I see similar code in linaro-image-tools; I guess this really was superseded?

Revision history for this message
James Tunnicliffe (dooferlad) wrote :

Indeed it was.

On 8 August 2011 22:51, Loïc Minier <email address hidden> wrote:
> This is marked as approved but not merged, but I see similar code in linaro-image-tools; I guess this really was superseded?
> --
> https://code.launchpad.net/~dooferlad/linaro-image-tools/fetch_image_gui/+merge/66917
> You are the owner of lp:~dooferlad/linaro-image-tools/fetch_image_gui.
>

--
James Tunnicliffe

Unmerged revisions

362. By James Tunnicliffe

Slight style clean up.

361. By James Tunnicliffe

Split unit test into three cases.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'fetch_image.py'
2--- fetch_image.py 1970-01-01 00:00:00 +0000
3+++ fetch_image.py 2011-07-05 15:28:36 +0000
4@@ -0,0 +1,79 @@
5+#!/usr/bin/env python
6+# Copyright (C) 2010, 2011 Linaro
7+#
8+# Author: James Tunnicliffe <james.tunnicliffe@linaro.org>
9+#
10+# This file is part of Linaro Image Tools.
11+#
12+# Linaro Image Tools is free software; you can redistribute it and/or
13+# modify it under the terms of the GNU General Public License
14+# as published by the Free Software Foundation; either version 2
15+# of the License, or (at your option) any later version.
16+#
17+# Linaro Image Tools is distributed in the hope that it will be useful,
18+# but WITHOUT ANY WARRANTY; without even the implied warranty of
19+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+# GNU General Public License for more details.
21+#
22+# You should have received a copy of the GNU General Public License
23+# along with Linaro Image Tools; if not, write to the Free Software
24+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
25+# USA.
26+
27+import sys
28+import os
29+import linaro_image_tools.FetchImage as FetchImage
30+import logging
31+
32+
33+def main():
34+ file_handler = FetchImage.FileHandler()
35+ config = FetchImage.FetchImageConfig()
36+
37+ # Unfortunately we need to do a bit of a hack here and look for some
38+ # options before performing a full options parse.
39+ clean_cache = ("--clean-cache" in sys.argv[1:]
40+ or "-x" in sys.argv[1:])
41+
42+ force_download = ("--force-download" in sys.argv[1:]
43+ or "-d" in sys.argv[1:])
44+
45+ if clean_cache:
46+ file_handler.clean_cache()
47+
48+ # If the settings file and server index need updating, grab them
49+ file_handler.update_files_from_server(force_download)
50+
51+ # Load settings YAML, which defines the parameters we ask for and
52+ # acceptable responses from the user
53+ config.read_config(file_handler.settings_file)
54+
55+ # Using the settings that the YAML defines as what we need for a build,
56+ # generate a command line parser and parse the command line
57+ config.parse_args(sys.argv[1:])
58+
59+ if config.args['platform'] == "snapshot":
60+ config.args['release_or_snapshot'] = "snapshot"
61+ else:
62+ config.args['release_or_snapshot'] = "release"
63+
64+ # Using the config we have, look up URLs to download data from in the
65+ # server index
66+ db = FetchImage.DB(file_handler.index_file)
67+
68+ image_url, hwpack_url = db.get_image_and_hwpack_urls(config.args)
69+
70+ if(image_url and hwpack_url):
71+
72+ tools_dir = os.path.dirname(__file__)
73+ if tools_dir == '':
74+ tools_dir = None
75+
76+ file_handler.create_media(image_url, hwpack_url,
77+ config.args, tools_dir)
78+ else:
79+ logging.error(
80+ "Unable to find files that match the parameters specified")
81+
82+if __name__ == '__main__':
83+ main()
84
85=== added file 'fetch_image_ui.py'
86--- fetch_image_ui.py 1970-01-01 00:00:00 +0000
87+++ fetch_image_ui.py 2011-07-05 15:28:36 +0000
88@@ -0,0 +1,1661 @@
89+#!/usr/bin/env python
90+# Copyright (C) 2010, 2011 Linaro
91+#
92+# Author: James Tunnicliffe <james.tunnicliffe@linaro.org>
93+#
94+# This file is part of Linaro Image Tools.
95+#
96+# Linaro Image Tools is free software; you can redistribute it and/or
97+# modify it under the terms of the GNU General Public License
98+# as published by the Free Software Foundation; either version 2
99+# of the License, or (at your option) any later version.
100+#
101+# Linaro Image Tools is distributed in the hope that it will be useful,
102+# but WITHOUT ANY WARRANTY; without even the implied warranty of
103+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
104+# GNU General Public License for more details.
105+#
106+# You should have received a copy of the GNU General Public License
107+# along with Linaro Image Tools; if not, write to the Free Software
108+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
109+# USA.
110+
111+import wx
112+import wx.wizard
113+import wx.wizard as wiz
114+import sys
115+import re
116+import os
117+import linaro_image_tools.FetchImage as FetchImage
118+import string
119+import unittest
120+import operator
121+import Queue
122+
123+
124+def add_button(bind_to,
125+ sizer,
126+ label,
127+ style,
128+ select_event,
129+ hover_event,
130+ unhover_event):
131+
132+ """Create a radio button with event bindings."""
133+ if(style != None):
134+ radio_button = wx.RadioButton(bind_to, label=label, style=style)
135+ else:
136+ radio_button = wx.RadioButton(bind_to, label=label)
137+
138+ sizer.Add(radio_button, 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5)
139+ bind_to.Bind(wx.EVT_RADIOBUTTON, select_event, radio_button)
140+ wx.EVT_ENTER_WINDOW(radio_button, hover_event)
141+ wx.EVT_LEAVE_WINDOW(radio_button, unhover_event)
142+
143+ return radio_button
144+
145+
146+class ReleaseOrSnapshotPage(wiz.PyWizardPage):
147+ """Ask the user if they want to use a release or a snapshot"""
148+
149+ def __init__(self, parent, config):
150+ wiz.PyWizardPage.__init__(self, parent)
151+ self.config = config
152+ self.settings = self.config.settings
153+ self.sizer = wx.BoxSizer(wx.VERTICAL)
154+ self.next = None
155+ self.prev = None
156+
157+ self.sizer.Add(wx.StaticText(self, -1,
158+"""This Wizard will write an operating system of your choosing to
159+either a disk image or to an MMC card. First we need to know if
160+your priority is stability or the latest and greatest features."""))
161+
162+ self.box1 = wx.BoxSizer(wx.VERTICAL)
163+
164+ self.button_text = {'release': "I would like to run stable, "
165+ "tested software.",
166+ 'snapshot': "I would like to run untested, but "
167+ "more up-to-date software."}
168+
169+ add_button(self, self.box1, self.button_text['release'],
170+ wx.RB_GROUP, self.event_radio_button_select, None, None)
171+
172+ # Save the setting for the default selected value
173+ self.settings['release_or_snapshot'] = "release"
174+
175+ add_button(self, self.box1, self.button_text['snapshot'], None,
176+ self.event_radio_button_select, None, None)
177+
178+ self.sizer.Add(self.box1, 0, wx.ALIGN_LEFT | wx.ALL, 5)
179+
180+ self.SetSizerAndFit(self.sizer)
181+ self.sizer.Fit(self)
182+ self.Move((50, 50))
183+
184+ def event_radio_button_select(self, event):
185+ self.radio_selected = event.GetEventObject().GetLabel()
186+ # The radio button can be release, snapshot or "latest snapshot"
187+ if(self.radio_selected == self.button_text['release']):
188+ self.settings['release_or_snapshot'] = "release"
189+ else:
190+ self.settings['release_or_snapshot'] = "snapshot"
191+
192+ def SetNext(self, next):
193+ self.next = next
194+
195+ def GetNext(self):
196+ return self.next
197+
198+
199+class AboutMyHardwarePage(wiz.WizardPageSimple):
200+ """Ask the user about their hardware. This only asks about the board, not
201+ any specific hardware packs because there can be multiple names for the
202+ same hardware pack or sometimes a hardware pack is only available in the
203+ releases or snapshots repository. We whittle down the choice as we go
204+ and the user can chose a hardare pack (if they don't like the default)
205+ under advanced options in the Linaro Media Create options
206+ page"""
207+
208+ def __init__(self, parent, config, db, width):
209+ wiz.WizardPageSimple.__init__(self, parent)
210+ self.settings = config.settings
211+ self.db = db
212+ self.sizer = wx.BoxSizer(wx.VERTICAL)
213+ self.box1 = wx.BoxSizer(wx.VERTICAL)
214+ self.box2 = wx.BoxSizer(wx.VERTICAL)
215+
216+ header = wx.StaticText(self,
217+ label = "Please select the hardware that you "
218+ "would like to build an image for from "
219+ "the following list")
220+
221+ header.Wrap(width - 10) # -10 because boarder below is 5 pixels wide
222+
223+ #--- Hardware Combo Box ---
224+ # Make sure that the displayed release is the one set in settings if
225+ # no selection is made
226+ if "panda" in self.settings['choice']['hardware'].keys():
227+ default_hardware = "panda"
228+ else:
229+ default_hardware = self.settings['choice']['hardware'].keys()[-1]
230+
231+ self.settings['hardware'] = default_hardware
232+ self.settings['compatable_hwpacks'] = (
233+ self.settings['choice']['hwpack'][self.settings['hardware']])
234+
235+ self.cb_hardware = wx.ComboBox(self,
236+ value =
237+ self.settings['choice']['hardware'][default_hardware],
238+ style = wx.CB_DROPDOWN | wx.CB_READONLY)
239+
240+ self.Bind(wx.EVT_COMBOBOX,
241+ self.event_combo_box_hardware,
242+ self.cb_hardware)
243+ self.box1.Add(self.cb_hardware, 0,
244+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5)
245+
246+ self.sizer.Add(header)
247+ self.sizer.Add(self.box1, 0, wx.ALIGN_LEFT | wx.ALL, 5)
248+ self.sizer.Add(self.box2, 0, wx.ALIGN_LEFT | wx.ALL, 5)
249+ self.SetSizerAndFit(self.sizer)
250+ self.sizer.Fit(self)
251+ self.Move((50, 50))
252+
253+ def on_page_changing(self):
254+ self.update_hardware_box()
255+
256+ def update_hardware_box(self):
257+ self.cb_hardware.Clear()
258+
259+ sorted_hardware_names = sorted(self.settings['choice']['hardware']
260+ .iteritems(),
261+ key=operator.itemgetter(1))
262+
263+ table = self.settings['release_or_snapshot'] + "_hwpacks"
264+
265+ for device_name, human_readable_name in sorted_hardware_names:
266+ for hwpack in self.settings['choice']['hwpack'][device_name]:
267+ if self.db.hardware_is_available_in_table(table, hwpack):
268+ self.cb_hardware.Append(human_readable_name, device_name)
269+ break
270+
271+ #--- Event(s) ---
272+ def event_combo_box_hardware(self, event):
273+ self.settings['hardware'] = (event
274+ .GetEventObject()
275+ .GetClientData(event.GetSelection())
276+ .encode('ascii'))
277+
278+ self.settings['compatable_hwpacks'] = (
279+ self.settings['choice']['hwpack'][self.settings['hardware']])
280+ #--- END event(s) ---
281+
282+
283+class SelectStableRelease(wiz.WizardPageSimple):
284+ """Ask the user which Linaro release they would like to run."""
285+ def __init__(self, parent, config, db, width):
286+ wiz.WizardPageSimple.__init__(self, parent)
287+ self.settings = config.settings
288+ self.db = db
289+ self.sizer = wx.BoxSizer(wx.VERTICAL)
290+ self.wizard = parent
291+
292+ header = wx.StaticText(self, label = "Please select the stable Linaro "
293+ "release you would like to use")
294+
295+ header.Wrap(width - 10) # -10 because boarder below is 5 pixels wide
296+
297+ self.sizer.Add(header)
298+ self.box1 = wx.BoxSizer(wx.VERTICAL)
299+
300+ platforms = []
301+ for key, value in self.settings['choice']['platform'].items():
302+ platforms.append(key)
303+
304+ default_release = self.settings['UI']['translate'][platforms[-1]]
305+ self.cb_release = wx.ComboBox(self,
306+ value = default_release,
307+ style = wx.CB_DROPDOWN | wx.CB_READONLY)
308+ self.Bind(wx.EVT_COMBOBOX,
309+ self.event_combo_box_release,
310+ self.cb_release)
311+
312+ if(default_release in self.settings['UI']['translate']):
313+ default_release = self.settings['UI']['translate'][default_release]
314+ self.settings['platform'] = (
315+ self.settings['UI']['reverse-translate'][default_release])
316+
317+ for item in platforms:
318+ if(item in self.settings['UI']['translate']):
319+ new_item = self.settings['UI']['translate'][item]
320+ item = new_item
321+
322+ self.cb_release.Append(item, item.upper())
323+
324+ self.cb_build = wx.ComboBox(self,
325+ style = wx.CB_DROPDOWN | wx.CB_READONLY)
326+ self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_build, self.cb_build)
327+
328+ self.box1.Add(self.cb_release, 0,
329+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5)
330+ self.box1.Add(self.cb_build, 0,
331+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5)
332+ self.sizer.Add(self.box1, 0, wx.ALIGN_LEFT | wx.ALL, 5)
333+ self.SetSizerAndFit(self.sizer)
334+ self.sizer.Fit(self)
335+ self.Move((50, 50))
336+
337+ def update_build_box(self):
338+ """Depending on what hardware has been chosen, the OS list may be
339+ restricted. Filter out anything that is unavailable."""
340+ self.cb_build.Clear()
341+
342+ builds = self.db.get_builds(self.settings['platform'])
343+ self.cb_build.SetValue("No build available")
344+
345+ for build in builds:
346+ if( self.db.hardware_is_available_for_platform_build(
347+ self.settings['compatable_hwpacks'],
348+ self.settings['platform'],
349+ build)
350+ and self.db.build_is_available_for_platform_image(
351+ "release_binaries",
352+ self.settings['platform'],
353+ self.settings['image'],
354+ build)):
355+
356+ self.cb_build.Append(build)
357+ self.cb_build.SetValue(build)
358+ self.settings['release_build'] = build
359+
360+ available_hwpacks = (
361+ self.db.get_available_hwpacks_for_hardware_build_plaform(
362+ self.settings['compatable_hwpacks'],
363+ self.settings['platform'],
364+ self.settings['release_build']))
365+
366+ if len(available_hwpacks):
367+ self.settings['hwpack'] = available_hwpacks[0]
368+ self.wizard.FindWindowById(wx.ID_FORWARD).Enable()
369+ else:
370+ self.wizard.FindWindowById(wx.ID_FORWARD).Disable()
371+
372+ def update_release_and_build_boxes(self):
373+ """Depending on what hardware has been chosen, some builds may be
374+ unavailable..."""
375+ self.cb_release.Clear()
376+
377+ default_release = None
378+ for platform, value in self.settings['choice']['platform'].items():
379+ if(self.db.hardware_is_available_for_platform(
380+ self.settings['compatable_hwpacks'],
381+ platform)
382+ and len(self.db.execute_return_list(
383+ 'select * from release_binaries '
384+ 'where platform == ? and image == ?',
385+ (platform, self.settings['image'])))):
386+
387+ if(platform in self.settings['UI']['translate']):
388+ platform = self.settings['UI']['translate'][platform]
389+
390+ self.cb_release.Append(platform, platform.upper())
391+ if not default_release or default_release < platform:
392+ default_release = platform
393+
394+ self.settings['platform'] = (
395+ self.settings['UI']['reverse-translate'][default_release])
396+ self.cb_release.SetValue(default_release)
397+ self.update_build_box()
398+
399+ #--- Event(s) ---
400+ def event_combo_box_release(self, evt):
401+ str = evt.GetString().encode('ascii').lower()
402+ if(str in self.settings['UI']['reverse-translate']):
403+ str = self.settings['UI']['reverse-translate'][str]
404+ self.settings['platform'] = str
405+
406+ self.update_build_box()
407+
408+ def event_combo_box_build(self, evt):
409+ self.settings['release_build'] = evt.GetString().encode('ascii')
410+ #--- END event(s) ---
411+
412+
413+class SelectSnapshot(wiz.WizardPageSimple):
414+ """Present the user with a calendar widget and a list of builds available
415+ on the selected date so they can chose a snapshot. Filter out days when
416+ their chosen hardware does not have an available build."""
417+
418+ def __init__(self, parent, config, db, width):
419+ wiz.WizardPageSimple.__init__(self, parent)
420+ self.settings = config.settings
421+ self.db = db
422+ self.wizard = parent
423+ self.width = width
424+ self.sizer = wx.BoxSizer(wx.VERTICAL)
425+
426+ header = wx.StaticText(self,
427+ label = "Builds are created most days. First "
428+ "please select the day on which the "
429+ "build you would like to use was built,"
430+ " then, if there was more than one "
431+ "build that day you will be able to "
432+ "select the build number.")
433+ header.Wrap(width - 10) # -10 because boarder below is 5 pixels wide
434+
435+ box1 = wx.BoxSizer(wx.VERTICAL)
436+ self.sizer.Add(header)
437+
438+ # Set today as the default build date in settings
439+ # (matches the date picker)
440+ self.today = wx.DateTime()
441+ self.today.SetToCurrent()
442+ self.settings['build_date'] = self.today.FormatISODate().encode('ascii')
443+
444+ dpc = wx.DatePickerCtrl(self, size = (120, -1),
445+ style = wx.DP_DEFAULT)
446+ self.Bind(wx.EVT_DATE_CHANGED, self.on_date_changed, dpc)
447+
448+ #--- Build number Combo Box ---
449+ # Make sure that the displayed build is the one set in settings if no
450+ # selection is made
451+ self.settings['build_number'] = 0
452+ self.update_build()
453+ self.cb_build = wx.ComboBox(self,
454+ style = wx.CB_DROPDOWN | wx.CB_READONLY)
455+ self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_build, self.cb_build)
456+
457+ #--- Layout ---
458+ # -- Combo boxes for hardware and image selection --
459+
460+ grid2 = wx.FlexGridSizer(0, 2, 0, 0)
461+ grid2.Add(dpc, 0, wx.ALIGN_LEFT | wx.ALL, 5)
462+ grid2.Add(self.cb_build, 0, wx.ALIGN_LEFT | wx.ALL, 5)
463+
464+ box1.Add(grid2, 0, wx.ALIGN_LEFT | wx.ALL, 5)
465+
466+ self.sizer.Add(box1, 0, wx.ALIGN_LEFT | wx.ALL, 5)
467+
468+ self.help_text = wx.StaticText(self)
469+ self.sizer.Add(self.help_text, 1, wx.EXPAND, 5)
470+
471+ self.SetSizer(self.sizer)
472+ self.sizer.Fit(self)
473+ self.Move((50, 50))
474+
475+ def update_platform(self):
476+ build_and_date = self.settings['snapshot_build'].split(":")
477+
478+ if len(build_and_date) == 2:
479+ self.settings['platform'] = (
480+ self.db.execute_return_list(
481+ "select platform from snapshot_binaries "
482+ "where date == ? and build == ?",
483+ (build_and_date[0], build_and_date[1])))
484+
485+ if len(self.settings['platform']) > 0:
486+ self.settings['platform'] = self.settings['platform'][0][0]
487+
488+ def update_build(self):
489+ small_date = re.sub('-', '', self.settings['build_date'])
490+ self.settings['snapshot_build'] = (small_date
491+ + ":"
492+ + str(self.settings['build_number']))
493+
494+ def fill_build_combo_box_for_date(self, date):
495+ """Every time a date is chosen, this function should be called. It will
496+ check to see if a compatible build is available. If there isn't, it
497+ will search for one and provide some help text to tell the user when
498+ compatable builds were built."""
499+ # Re-populate the build combo box
500+
501+ self.cb_build.Clear()
502+
503+ builds = self.db.get_binary_builds_on_day_from_db(
504+ self.settings['image'],
505+ date,
506+ self.settings['compatable_hwpacks'])
507+
508+ if len(builds):
509+ max = 0
510+ for item in builds:
511+ #Always get a tuple, only interested in the first entry
512+ item = item[0]
513+ self.cb_build.Append(item, item.upper())
514+
515+ if item > max:
516+ max = item
517+
518+ self.cb_build.SetValue(max)
519+ self.wizard.FindWindowById(wx.ID_FORWARD).Enable()
520+ self.help_text.SetLabel("")
521+
522+ else:
523+ self.cb_build.SetValue("No builds available")
524+ future_date, past_date = self.db.get_next_prev_day_with_builds(
525+ self.settings['image'],
526+ date,
527+ self.settings['compatable_hwpacks'])
528+
529+ help_text = None
530+
531+ if future_date and past_date:
532+ help_text = ("There are no builds that match your "
533+ "specifications available on the selected date. "
534+ "The previous build was on " + past_date +
535+ " and the next build was on " + future_date + ".")
536+ elif future_date:
537+ help_text = ("There are no builds that match your "
538+ "specifications available on the selected date. "
539+ "The next build was on " + future_date +
540+ " and I couldn't find a past build (looked one "
541+ "year back from the selected date).")
542+ elif past_date:
543+ help_text = ("There are no builds that match your "
544+ "specifications available on the selected date. "
545+ "The previous build was on " + past_date)
546+ if date != self.today.FormatISODate().encode('ascii'):
547+ help_text += (" and I couldn't find a future build (I "
548+ "looked up to one year forward from the "
549+ "selected date).")
550+ else:
551+ help_text = ("I could not find any builds that match your "
552+ "specifications close to the selected date (I "
553+ "looked forward and back one year from the "
554+ "selected date).")
555+
556+ self.help_text.SetLabel(help_text)
557+ self.help_text.Wrap(self.width - 10)
558+ self.wizard.FindWindowById(wx.ID_FORWARD).Disable()
559+
560+ #--- Event(s) ---
561+ def on_date_changed(self, evt):
562+ self.settings['build_date'] = evt.GetDate().FormatISODate().encode('ascii')
563+ self.fill_build_combo_box_for_date(self.settings['build_date'])
564+ self.update_build()
565+
566+ def event_combo_box_build(self, evt):
567+ self.settings['build_number'] = evt.GetString().encode('ascii').lower()
568+ self.update_build()
569+ #--- END event(s) ---
570+
571+
572+class SelectOS(wiz.WizardPageSimple):
573+ """Ask the user which OS they would like to run. Filter out any choices
574+ that are unavailable due to previous choices."""
575+ def __init__(self, parent, config, db, width):
576+ wiz.WizardPageSimple.__init__(self, parent)
577+ self.settings = config.settings
578+ self.wizard = parent
579+ self.db = db
580+ self.width = width
581+ self.sizer = wx.BoxSizer(wx.VERTICAL)
582+ self.settings['image'] = None
583+
584+ header = wx.StaticText(self, label = "Please select the operating "
585+ "system you would like to run on "
586+ "your hardware.")
587+ header.Wrap(width - 10) # -10 because boarder below is 5 pixels wide
588+
589+ self.box1 = wx.BoxSizer(wx.VERTICAL)
590+ self.sizer.Add(header)
591+
592+ self.cb_image = wx.ComboBox(self,
593+ style = wx.CB_DROPDOWN | wx.CB_READONLY)
594+ self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_os, self.cb_image)
595+
596+ #--- Layout ---
597+ # -- Combo boxes for hardware and image selection --
598+ self.box1.Add(self.cb_image, 0, wx.ALIGN_LEFT | wx.ALL, 5)
599+
600+ self.sizer.Add(self.box1, 0, wx.ALIGN_LEFT | wx.ALL, 5)
601+
602+ self.help_text = wx.StaticText(self)
603+ self.sizer.Add(self.help_text, 1, wx.EXPAND, 5)
604+
605+ self.SetSizer(self.sizer)
606+ self.sizer.Fit(self)
607+ self.Move((50, 50))
608+
609+ def get_human_os_name(self, item):
610+ """Given an OS name from the database, return a human name (either
611+ translated from the YAML settings, or just prettified) and if it is a
612+ LEB OS or not"""
613+
614+ item = re.sub("linaro-", "", item) # Remove any linaro- decoration
615+
616+ human_name = item
617+
618+ if item in self.settings['UI']['descriptions']:
619+ human_name = self.settings['UI']['descriptions'][item]
620+ else:
621+ # Make human_name look nicer...
622+ human_name = string.capwords(item)
623+
624+ leb_search = re.search("^LEB:\s*(.*)$", human_name)
625+
626+ if leb_search:
627+ return leb_search.group(1), True
628+
629+ return human_name, False
630+
631+ def fill_os_list(self):
632+ """Filter the list of OS's from the config file based on the users
633+ preferences so all choices in the list are valid (i.e. their hardware
634+ is supported for the build they have chosen)."""
635+
636+ # select unique image from snapshot_binaries/release_binaries to
637+ # generate list
638+ os_list = None
639+ if self.settings['release_or_snapshot'] == "release":
640+ os_list = self.db.get_os_list_from('release_binaries')
641+ else:
642+ os_list = self.db.get_os_list_from('snapshot_binaries')
643+
644+ self.cb_image.Clear()
645+
646+ printed_tag = None
647+ last_name = None
648+ current_image_setting_valid = False
649+
650+ for state in ["LEB", "other"]:
651+ for item in os_list:
652+ if item == "old":
653+ # Old is a directory that sometimes hangs around,
654+ # but isn't one we want to display
655+ continue
656+
657+ # Save the original, untouched image name for use later.
658+ # We give it a more human name for display
659+ original = item
660+ item = re.sub("linaro-", "", item)
661+
662+ os_hardware_combo_available = (
663+ self.db.image_hardware_combo_available(
664+ self.settings['release_or_snapshot'],
665+ original,
666+ self.settings['compatable_hwpacks']))
667+
668+ if os_hardware_combo_available:
669+ human_name, is_LEB = self.get_human_os_name(item)
670+
671+ if item == self.settings['image']:
672+ current_image_setting_valid = True
673+
674+ if state == "LEB" and is_LEB:
675+
676+ if printed_tag != state:
677+ self.cb_image.Append(
678+ "- Linaro Supported Releases -")
679+ printed_tag = state
680+
681+ self.cb_image.Append(human_name, original)
682+
683+ if self.settings['image'] == None:
684+ self.settings['image'] = original
685+
686+ elif state != "LEB" and not is_LEB:
687+ if printed_tag != state:
688+ self.cb_image.Append(
689+ "- Community Supported Releases -")
690+ printed_tag = state
691+
692+ self.cb_image.Append(human_name, original)
693+
694+ last_name = original
695+
696+ if( self.settings['image'] != None
697+ and current_image_setting_valid == False):
698+ # If we have an image setting, but it doesn't match the OS list, we
699+ # have switched OS list. It may be that adding/removing "linaro-"
700+ # from the name will get a match.
701+
702+ if re.search("linaro-", self.settings['image']):
703+ test_name = re.sub("linaro-", "", self.settings['image'])
704+ else:
705+ test_name = "linaro-" + self.settings['image']
706+
707+ if test_name in os_list:
708+ # Success! We have translated the name and can retain the
709+ # "old setting"
710+ self.settings['image'] = test_name
711+ current_image_setting_valid = True
712+
713+ if( self.settings['image'] == None
714+ or current_image_setting_valid == False):
715+ # This should only get hit if there are no LEBs available
716+ self.settings['image'] = last_name
717+
718+ assert self.settings['image']
719+
720+ # Make sure the visible selected value matches the saved setting
721+ self.cb_image.SetValue(
722+ self.get_human_os_name(self.settings['image'])[0])
723+
724+ #--- Event(s) ---
725+ def event_combo_box_os(self, evt):
726+ self.settings['image'] = self.cb_image.GetClientData(
727+ evt.GetSelection())
728+
729+ if self.settings['image']: # Is None for items that aren't an OS
730+ self.wizard.FindWindowById(wx.ID_FORWARD).Enable()
731+ image = re.sub("linaro-", "", self.settings['image'])
732+
733+ if image + "::long" in self.settings['UI']['descriptions']:
734+ self.help_text.SetLabel(self.settings['UI']
735+ ['descriptions']
736+ [image + "::long"])
737+ else:
738+ self.help_text.SetLabel("")
739+
740+ else: # Have selected help text
741+ self.wizard.FindWindowById(wx.ID_FORWARD).Disable()
742+ self.help_text.SetLabel("Please select an operating system to run "
743+ "on your chosen hardware.")
744+
745+ self.help_text.Wrap(self.width - 10)
746+ #--- END event(s) ---
747+
748+
749+class LMC_settings(wiz.WizardPageSimple):
750+ """Present the user with, intially, the choice of writing the file system
751+ they are going to have created to a file, or directly to a device. Ask
752+ which file/device to write to.
753+
754+ If writing to a device, the user is asked to tick a box saying that they
755+ understand that the device they have chosen will be erased.
756+
757+ If the user ticks the advanced box, more options are shown."""
758+
759+ def __init__(self, parent, config, db, width):
760+ wiz.WizardPageSimple.__init__(self, parent)
761+ self.settings = config.settings
762+ self.wizard = parent
763+ self.sizer = wx.BoxSizer(wx.VERTICAL)
764+ self.yes_use_mmc = False
765+ self.db = db
766+
767+ self.settings['path_selected'] = ""
768+
769+ header = wx.StaticText(self,
770+ label = "Media Creation Settings\n\n"
771+ "Please select if you would like to write the "
772+ "file system I am about to create to a memory "
773+ "card, or to a file on the local file system.")
774+ header.Wrap(width - 10) # -10 because boarder below is 5 pixels wide
775+
776+ #--- Build some widgets ---
777+ #-- Target file system --
778+ file_systems = ["ext3", "ext4", "btrfs", "ext2"]
779+ default_target = file_systems[0]
780+ self.settings['rootfs'] = default_target
781+ cb_rootfs = wx.ComboBox(self,
782+ value = default_target,
783+ style = wx.CB_DROPDOWN | wx.CB_READONLY)
784+
785+ for item in file_systems:
786+ cb_rootfs.Append(item, item.upper())
787+
788+ self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_rootfs, cb_rootfs)
789+
790+ #-- Image size spinner
791+ self.image_size_spinner = wx.SpinCtrl(self, -1, "")
792+ self.Bind(wx.EVT_SPINCTRL,
793+ self.event_image_size,
794+ self.image_size_spinner)
795+
796+ #-- Swap size spinner
797+ self.swap_size_spinner = wx.SpinCtrl(self, -1, "")
798+ self.Bind(wx.EVT_SPINCTRL,
799+ self.event_swap_size,
800+ self.swap_size_spinner)
801+
802+ #--- Layout ---
803+ self.sizer.Add(header, 0, wx.ALIGN_LEFT | wx.ALL, 5)
804+ box1 = wx.BoxSizer(wx.VERTICAL)
805+ file_dev_grid = wx.FlexGridSizer(0, 2, 0, 0)
806+ box1.Add(file_dev_grid, 0, wx.EXPAND)
807+ grid1 = wx.FlexGridSizer(0, 2, 0, 0)
808+
809+ # self.settings['write_to_file_or_device'] should match the first
810+ # button below...
811+ self.settings['write_to_file_or_device'] = "file"
812+ add_button(self,
813+ file_dev_grid,
814+ "Write to file",
815+ wx.RB_GROUP,
816+ self.event_radio_button_select,
817+ None, None)
818+
819+ add_button(self,
820+ file_dev_grid,
821+ "Write to device",
822+ None,
823+ self.event_radio_button_select,
824+ None, None)
825+
826+ self.help_text_values = {"device": "Please select a device to write "
827+ "the file system to:",
828+ "file": "Please select a file to write the "
829+ "file system to:"}
830+
831+ self.help_text = wx.StaticText(
832+ self,
833+ label =
834+ self.help_text_values[
835+ self.settings['write_to_file_or_device']])
836+ self.help_text.Wrap(width - 10)
837+
838+ #-- File/dev picker --
839+ file_browse_button = wx.Button(self, -1, "Browse")
840+ file_browse_grid = wx.FlexGridSizer(0, 2, 0, 0)
841+ self.file_path_and_name = wx.TextCtrl(self, -1, "", size=(300, -1))
842+
843+ file_browse_grid.Add(self.file_path_and_name, 0, wx.EXPAND)
844+ file_browse_grid.Add(file_browse_button, 0, wx.EXPAND)
845+
846+ self.Bind(wx.EVT_BUTTON,
847+ self.event_open_file_control,
848+ file_browse_button)
849+
850+ self.Bind(wx.EVT_TEXT,
851+ self.event_file_path_and_name,
852+ self.file_path_and_name)
853+
854+ box1.Add(self.help_text, 0, wx.ALIGN_LEFT | wx.ALL, 5)
855+
856+ box1.Add(file_browse_grid, 0, wx.EXPAND)
857+
858+ cb1 = wx.CheckBox(self, -1, "Show advanced options")
859+ self.Bind(wx.EVT_CHECKBOX, self.event_show_advanced_options, cb1)
860+ box1.Add(cb1)
861+
862+ #-- Combo boxes for hardware and image selection --
863+ optional_settings_box_title = wx.StaticBox(
864+ self,
865+ label = " Optional Settings ")
866+
867+ self.optional_settings_box = wx.StaticBoxSizer(
868+ optional_settings_box_title,
869+ wx.VERTICAL)
870+
871+ self.box2 = wx.BoxSizer(wx.VERTICAL)
872+
873+ self.box2.AddWindow(self.optional_settings_box,
874+ 0,
875+ border=2,
876+ flag=wx.ALL | wx.EXPAND)
877+
878+ grid1.Add(cb_rootfs, 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5)
879+
880+ grid1.Add(wx.StaticText(self,
881+ label = "The root file system of the image"),
882+ 0,
883+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
884+ 5)
885+
886+ # We want to sub-devide the cell, to add another grid sizer...
887+ file_size_grid = wx.FlexGridSizer(0, 2, 0, 0)
888+
889+ grid1.Add(file_size_grid,
890+ 0,
891+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP)
892+
893+ # Add a spinner that allows us to type/click a numerical value (defined above)
894+ file_size_grid.Add(self.image_size_spinner,
895+ 0,
896+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
897+ 5)
898+
899+ # Add a choice of MB or GB for size input
900+ units = ["GB", "MB"]
901+ self.size_unit = units[0] # Set the default unit
902+ unit_choice = wx.Choice(self, -1, (100, 50), choices = units)
903+ self.Bind(wx.EVT_CHOICE, self.event_chose_unit, unit_choice)
904+ file_size_grid.Add(unit_choice, 0, wx.ALIGN_RIGHT | wx.TOP, 5)
905+
906+ # Back out of the extra grid, add some help text
907+ grid1.Add(wx.StaticText(
908+ self,
909+ label = "Writing to file only: Image file size"),
910+ 0,
911+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
912+ 5)
913+
914+ # The swap size (MB only)
915+ grid1.Add(self.swap_size_spinner,
916+ 0,
917+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
918+ 5)
919+
920+ grid1.Add(wx.StaticText(self, label = "Swap file size in MB"),
921+ 0,
922+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
923+ 5)
924+
925+ self.cb_hwpacks = wx.ComboBox(
926+ self,
927+ value = self.settings['compatable_hwpacks'][0],
928+ style = wx.CB_DROPDOWN | wx.CB_READONLY)
929+
930+ self.Bind(wx.EVT_COMBOBOX,
931+ self.event_combo_box_hwpack,
932+ self.cb_hwpacks)
933+
934+ grid1.Add(self.cb_hwpacks,
935+ 0,
936+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
937+ 5)
938+
939+ grid1.Add(wx.StaticText(self, label = "Compatible hardware packs"),
940+ 0,
941+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
942+ 5)
943+
944+ self.optional_settings_box.Add(grid1, 0, wx.ALIGN_LEFT | wx.ALL, 5)
945+
946+ confirm_mmc_usage_title = wx.StaticBox(self, label = " Are you sure? ")
947+
948+ self.confirm_mmc_usage_box = wx.StaticBoxSizer(confirm_mmc_usage_title,
949+ wx.VERTICAL)
950+ cb2 = wx.CheckBox(
951+ self,
952+ -1,
953+ "Yes, erase and use the device I have selected above.")
954+
955+ self.Bind(wx.EVT_CHECKBOX, self.event_use_mmc_tickbox, cb2)
956+ self.confirm_mmc_usage_box.Add(cb2)
957+
958+ self.box3 = wx.BoxSizer(wx.VERTICAL)
959+ self.box3.AddWindow(self.confirm_mmc_usage_box,
960+ 0,
961+ border=2,
962+ flag=wx.ALL | wx.EXPAND)
963+
964+ self.sizer.Add(box1, 0, wx.ALIGN_LEFT | wx.ALL, 0)
965+ self.sizer.Add(self.box2, 0, wx.ALIGN_LEFT | wx.ALL, 0)
966+ self.sizer.Add(self.box3, 0, wx.ALIGN_LEFT | wx.ALL, 0)
967+ self.SetSizer(self.sizer)
968+ self.sizer.Fit(self)
969+ self.Move((50, 50))
970+
971+ def on_activate(self):
972+ self.update_forward_active_and_mmc_confirm_box_visible()
973+ self.set_hwpacks_for_hardware()
974+
975+ def set_hwpacks_for_hardware(self):
976+ self.cb_hwpacks.Clear()
977+
978+ if self.settings['release_or_snapshot'] == "snapshot":
979+ self.settings['build'] = self.settings['snapshot_build']
980+
981+ date_and_build = self.settings['build'].split(":")
982+
983+ compatable_hwpacks = (
984+ self.db.get_available_hwpacks_for_hardware_snapshot_build(
985+ self.settings['compatable_hwpacks'],
986+ self.settings['platform'],
987+ date_and_build[0],
988+ date_and_build[1]))
989+ else:
990+ self.settings['build'] = self.settings['release_build']
991+ compatable_hwpacks = (
992+ self.db.get_available_hwpacks_for_hardware_build_plaform(
993+ self.settings['compatable_hwpacks'],
994+ self.settings['platform'],
995+ self.settings['build']))
996+
997+ for hwpack in compatable_hwpacks:
998+ self.cb_hwpacks.Append(hwpack)
999+
1000+ self.cb_hwpacks.SetStringSelection(compatable_hwpacks[0])
1001+ self.settings['hwpack'] = compatable_hwpacks[0]
1002+
1003+ def update_forward_active_and_mmc_confirm_box_visible(self):
1004+ if( self.settings['path_selected']
1005+ and self.settings['path_selected'] != ""):
1006+
1007+ if ( self.settings['write_to_file_or_device'] == "file"
1008+ or self.settings['write_to_file_or_device'] == "device"
1009+ and self.yes_use_mmc):
1010+ self.wizard.FindWindowById(wx.ID_FORWARD).Enable()
1011+ else:
1012+ self.wizard.FindWindowById(wx.ID_FORWARD).Disable()
1013+ else:
1014+ self.wizard.FindWindowById(wx.ID_FORWARD).Disable()
1015+
1016+ if self.settings['write_to_file_or_device'] == "device":
1017+ self.box3.Show(self.confirm_mmc_usage_box, True)
1018+ else:
1019+ self.box3.Hide(self.confirm_mmc_usage_box, True)
1020+
1021+ # --- Event Handlers ---
1022+ def event_open_file_control(self, event):
1023+ if self.settings['write_to_file_or_device'] == "file":
1024+
1025+ dlg = wx.FileDialog(self,
1026+ message="Save file as ...",
1027+ defaultDir=os.getcwd(),
1028+ defaultFile="",
1029+ style=wx.SAVE)
1030+
1031+ elif self.settings['write_to_file_or_device'] == "device":
1032+ dlg = wx.FileDialog(self,
1033+ message="Choose a device",
1034+ defaultDir=os.getcwd(),
1035+ defaultFile="",
1036+ style=wx.OPEN | wx.CHANGE_DIR)
1037+
1038+ if dlg.ShowModal() == wx.ID_OK:
1039+ self.settings['path_selected'] = dlg.GetPaths()[0]
1040+ self.file_path_and_name.SetValue(self.settings['path_selected'])
1041+
1042+ dlg.Destroy()
1043+ self.update_forward_active_and_mmc_confirm_box_visible()
1044+
1045+ def event_file_path_and_name(self, event):
1046+ self.settings['path_selected'] = event.GetString()
1047+ self.update_forward_active_and_mmc_confirm_box_visible()
1048+
1049+ def event_combo_box_hwpack(self, event):
1050+ self.settings['hwpack'] = event.GetString().encode('ascii')
1051+
1052+ def event_combo_box_rootfs(self, evt):
1053+ self.settings['rootfs'] = evt.GetString().encode('ascii').lower()
1054+
1055+ def event_radio_button_select(self, event):
1056+ """Search the label of the button that has been selected to work out
1057+ what we are writing to."""
1058+ setting_search = re.search(
1059+ "write to (\w+)",
1060+ event
1061+ .GetEventObject()
1062+ .GetLabel()
1063+ .encode('ascii')
1064+ .lower())
1065+
1066+ assert setting_search
1067+
1068+ self.settings['write_to_file_or_device'] = setting_search.group(1)
1069+
1070+ self.help_text.SetLabel(
1071+ self.help_text_values[self.settings['write_to_file_or_device']])
1072+
1073+ self.update_forward_active_and_mmc_confirm_box_visible()
1074+
1075+ def event_show_advanced_options(self, event):
1076+ if event.IsChecked():
1077+ self.box2.Show(self.optional_settings_box, True)
1078+ else:
1079+ self.box2.Hide(self.optional_settings_box, True)
1080+
1081+ def event_pick_file_path(self, evt):
1082+ self.settings['path_selected'] = os.path.abspath(evt.GetPath())
1083+ self.update_forward_active_and_mmc_confirm_box_visible()
1084+
1085+ def update_image_size_setting(self):
1086+ if(self.image_size_spinner.GetValue() > 0):
1087+ self.settings['image_size'] = (str(self.image_size_spinner
1088+ .GetValue())
1089+ + self.size_unit[0])
1090+ else:
1091+ self.settings['image_size'] = None
1092+
1093+ def event_image_size(self, event):
1094+ self.update_image_size_setting()
1095+
1096+ def event_chose_unit(self, event):
1097+ self.size_unit = event.GetString()
1098+ self.update_image_size_setting()
1099+
1100+ def event_swap_size(self, event):
1101+ self.settings['swap_file'] = str(self.image_size_spinner.GetValue())
1102+
1103+ def event_use_mmc_tickbox(self, event):
1104+ self.yes_use_mmc = event.IsChecked()
1105+ self.update_forward_active_and_mmc_confirm_box_visible()
1106+
1107+
1108+class RunLMC(wiz.WizardPageSimple):
1109+ """Present the user with some information about their choices and a button
1110+ to start linaro-media-create. The linaro-media-create process is started in
1111+ a new thread and important events are communicated back to the UI through a
1112+ queue."""
1113+
1114+ def __init__(self, parent, config, db, width):
1115+ wiz.WizardPageSimple.__init__(self, parent)
1116+ self.settings = config.settings
1117+ self.sizer = wx.BoxSizer(wx.VERTICAL)
1118+ self.db = db
1119+ self.width = width
1120+ self.wizard = parent
1121+
1122+ header = wx.StaticText(self, label = """Installing...""")
1123+ header.Wrap(width - 10) # -10 because boarder below is 5 pixels wide
1124+
1125+ self.sizer.Add(header)
1126+ self.box1 = wx.BoxSizer(wx.VERTICAL)
1127+
1128+ # We expect to print 4 lines of information, reserve space using blank
1129+ # lines.
1130+ self.settings_summary_text = wx.StaticText(self, label = "\n\n\n\n")
1131+ self.settings_summary_text.Wrap(width - 10)
1132+
1133+ self.box1.Add(self.settings_summary_text, 0, wx.ALIGN_LEFT | wx.ALL, 5)
1134+
1135+ self.start_button = wx.Button(self, 10, "Start", (20, 20))
1136+ self.Bind(wx.EVT_BUTTON, self.start_lmc, self.start_button)
1137+
1138+ self.start_button.SetToolTipString("Start creating an image, using the"
1139+ "above settings.")
1140+
1141+ self.start_button.SetSize(self.start_button.GetBestSize())
1142+ self.box1.Add(self.start_button, 0, wx.ALIGN_LEFT | wx.ALL, 5)
1143+
1144+ self.status_grid = wx.FlexGridSizer(0, 2, 0, 0)
1145+
1146+ self.status_grid.Add(wx.StaticText(self, label="Downloading files"),
1147+ 0,
1148+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
1149+ 5)
1150+
1151+ self.downloading_files_status = wx.StaticText(self, label="")
1152+ self.status_grid.Add(self.downloading_files_status,
1153+ 0,
1154+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
1155+ 5)
1156+
1157+ self.status_grid.Add(wx.StaticText(self, label="Unpacking downloads"),
1158+ 0,
1159+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
1160+ 5)
1161+
1162+ self.unpacking_files_status = wx.StaticText(self, label="")
1163+
1164+ self.status_grid.Add(self.unpacking_files_status,
1165+ 0,
1166+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
1167+ 5)
1168+
1169+ self.status_grid.Add(wx.StaticText(self, label="Installing packages"),
1170+ 0,
1171+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
1172+ 5)
1173+
1174+ self.installing_packages_status = wx.StaticText(self, label="")
1175+
1176+ self.status_grid.Add(self.installing_packages_status,
1177+ 0,
1178+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
1179+ 5)
1180+
1181+ self.status_grid.Add(wx.StaticText(self, label="Create file system"),
1182+ 0,
1183+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
1184+ 5)
1185+
1186+ self.create_file_system_status = wx.StaticText(self, label="")
1187+
1188+ self.status_grid.Add(self.create_file_system_status,
1189+ 0,
1190+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
1191+ 5)
1192+
1193+ self.status_grid.Add(wx.StaticText(self, label="Populate file system"),
1194+ 0,
1195+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
1196+ 5)
1197+
1198+ self.populate_file_system_status = wx.StaticText(self, label="")
1199+
1200+ self.status_grid.Add(self.populate_file_system_status,
1201+ 0,
1202+ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
1203+ 5)
1204+
1205+ self.sizer.Add(self.box1, 0, wx.ALIGN_LEFT | wx.ALL, 5)
1206+ self.sizer.Add(self.status_grid, 0, wx.ALIGN_LEFT | wx.ALL, 5)
1207+ self.SetSizerAndFit(self.sizer)
1208+ self.sizer.Fit(self)
1209+ self.Move((50, 50))
1210+
1211+ def on_activate(self):
1212+ """Called just before the page is displayed to update the text based on
1213+ the users preferences."""
1214+
1215+ # The build is stored in different forms depending on if we are using a
1216+ # release or snapshot but from here on in it is a common value
1217+ if self.settings['release_or_snapshot'] == "snapshot":
1218+ self.settings['build'] = self.settings['snapshot_build']
1219+ else:
1220+ self.settings['build'] = self.settings['release_build']
1221+
1222+ settings_summary = ("Press start to create an image with the "
1223+ "following settings:\n")
1224+ settings_summary += "Operating System: " + self.settings['image'] + "\n"
1225+ settings_summary += "Hardware: " + self.settings['hardware'] + "\n"
1226+
1227+ # Assumption is that a file may be in a long path, we don't know how
1228+ # big the font is and we don't want to allow the path to run off the
1229+ # end of the line, so if a file is chosen, just show the file name.
1230+ # Devices are (probably) /dev/some_short_name and the user really needs
1231+ # to check them, so we show the whole thing.
1232+ path = self.settings['path_selected']
1233+ if self.settings['write_to_file_or_device'] == "file":
1234+ path = self.settings['path_selected'].split(os.sep)[-1]
1235+
1236+ settings_summary += ( "Writing image to "
1237+ + self.settings['write_to_file_or_device']
1238+ + " "
1239+ + path)
1240+
1241+ self.settings_summary_text.SetLabel(settings_summary)
1242+ self.settings_summary_text.Wrap(self.width - 10)
1243+
1244+ def start_lmc(self, event):
1245+ """Start a thread that runs linaro-media-create and a timer, which
1246+ checks for UI updates every 100ms"""
1247+
1248+ if self.settings['write_to_file_or_device'] == "file":
1249+ self.settings['image_file'] = self.settings['path_selected']
1250+ elif self.settings['write_to_file_or_device'] == "device":
1251+ self.settings['mmc'] = self.settings['path_selected']
1252+ else:
1253+ assert False, ("self.config.settings['write_to_file_or_device'] "
1254+ "was an unexpected value"
1255+ + self.settings['write_to_file_or_device'])
1256+
1257+ image_url, hwpack_url = self.db.get_image_and_hwpack_urls(self.settings)
1258+
1259+ # Currently the UI is blocked when LMC is running, so grey out the
1260+ # buttons to indicate to the user that they won't work!
1261+ self.wizard.FindWindowById(wx.ID_BACKWARD).Disable()
1262+ self.wizard.FindWindowById(wx.ID_CANCEL).Disable()
1263+
1264+ if(image_url and hwpack_url):
1265+
1266+ print image_url
1267+ print hwpack_url
1268+
1269+ self.file_handler = FetchImage.FileHandler()
1270+
1271+ tools_dir = os.path.dirname(__file__)
1272+ if tools_dir == '':
1273+ tools_dir = None
1274+
1275+ self.file_handler.create_media(image_url,
1276+ hwpack_url,
1277+ self.settings,
1278+ tools_dir,
1279+ True,
1280+ self)
1281+
1282+ self.timer = wx.Timer(self)
1283+ self.Bind(wx.EVT_TIMER, self.timer_ping, self.timer)
1284+ self.timer.Start(milliseconds=100, oneShot=True)
1285+
1286+ self.start_button.Disable()
1287+ self.event_queue = Queue.Queue()
1288+ self.file_handler.start_lmc_gui_thread(self.event_queue)
1289+ else:
1290+ print >> sys.stderr, ("Unable to find files that match the"
1291+ "parameters specified")
1292+
1293+ def timer_ping(self, event):
1294+ """During start_lmc a timer is started to poll for events from
1295+ linaro-media-create every 100ms. This is the function which is called
1296+ to do that polling."""
1297+
1298+ if self.event_queue.empty() == False:
1299+ event = self.event_queue.get()
1300+
1301+ if event[0] == "start":
1302+ self.event_start(event[1])
1303+ self.timer.Start(milliseconds=100, oneShot=True)
1304+
1305+ elif event[0] == "end":
1306+ self.event_end(event[1])
1307+ self.timer.Start(milliseconds=100, oneShot=True)
1308+
1309+ elif event == "terminate":
1310+ # Process complete. Enable next button.
1311+ self.wizard.FindWindowById(wx.ID_FORWARD).Enable()
1312+ self.populate_file_system_status.SetLabel("Done")
1313+
1314+ else:
1315+ print >> sys.stderr, "timer_ping: Unhandled event", event
1316+
1317+ else:
1318+ self.timer.Start(milliseconds=100, oneShot=True)
1319+
1320+ def unsigned_packages_query(self, package_list):
1321+ message = ('In order to continue, I need to install some unsigned'
1322+ 'packages into the image. Is this OK? The packages are:'
1323+ '\n\n' + package_list)
1324+
1325+ dlg = wx.MessageDialog(self,
1326+ message,
1327+ 'Install Unsigned Packages Into Image?',
1328+ wx.YES_NO | wx.NO_DEFAULT)
1329+
1330+ choice = dlg.ShowModal()
1331+ dlg.Destroy()
1332+
1333+ return choice == wx.ID_YES
1334+
1335+ #--- Event(s) ---
1336+ def event_start(self, event):
1337+ if event == "download OS":
1338+ self.downloading_files_status.SetLabel("Downloading OS")
1339+ elif event == "download hwpack":
1340+ self.downloading_files_status.SetLabel("Downloading Hardware Pack")
1341+ elif event == "unpack":
1342+ self.unpacking_files_status.SetLabel("Running")
1343+ elif event == "installing packages":
1344+ self.installing_packages_status.SetLabel("Running")
1345+
1346+ elif re.search('^unverified_packages:', event):
1347+ # Get rid of event ID and whitespace invariance
1348+ packages = " ".join(event.split()[1:])
1349+ install_unsigned_packages = self.unsigned_packages_query(packages)
1350+
1351+ if install_unsigned_packages == False:
1352+ self.file_handler.kill_create_media()
1353+ sys.exit(1)
1354+ else:
1355+ self.file_handler.send_to_create_process("y")
1356+
1357+ elif event == "create file system":
1358+ self.create_file_system_status.SetLabel("Running")
1359+ elif event == "populate file system":
1360+ self.populate_file_system_status.SetLabel("Running")
1361+ else:
1362+ print "Unhandled start event:", event
1363+
1364+ def event_end(self, event):
1365+ if event == "download OS":
1366+ self.downloading_files_status.SetLabel("Done (1/2)")
1367+ elif event == "download hwpack":
1368+ self.downloading_files_status.SetLabel("Done")
1369+ elif event == "unpack":
1370+ self.unpacking_files_status.SetLabel("Done")
1371+ elif event == "installing packages":
1372+ self.installing_packages_status.SetLabel("Done")
1373+ elif event == "create file system":
1374+ self.create_file_system_status.SetLabel("Done")
1375+ elif event == "populate file system":
1376+ self.populate_file_system_status.SetLabel("Done")
1377+ else:
1378+ print "Unhhandled end event:", event
1379+
1380+ def event_combo_box_release(self, evt):
1381+ pass
1382+
1383+ def event_combo_box_build(self, evt):
1384+ pass
1385+ #--- END event(s) ---
1386+
1387+
1388+class TestDriveWizard(wx.wizard.Wizard):
1389+ def __init__(self, title):
1390+ wx.wizard.Wizard.__init__(self, None, -1, title, wx.NullBitmap)
1391+ self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.on_page_changing)
1392+ self.done_startup = False
1393+
1394+ def on_page_changing(self, evt):
1395+ 'Executed before the page changes.'
1396+
1397+ if self.done_startup == False:
1398+ self.pages['lmc_settings'].box2.Hide(
1399+ self.pages['lmc_settings'].optional_settings_box,
1400+ True)
1401+
1402+ self.pages['lmc_settings'].box3.Hide(
1403+ self.pages['lmc_settings'].confirm_mmc_usage_box,
1404+ True)
1405+
1406+ self.done_startup = True
1407+
1408+ page = evt.GetPage()
1409+
1410+ if evt.GetDirection(): # If going forwards...
1411+ # Always enable back button if going forwards
1412+ self.wizard.FindWindowById(wx.ID_BACKWARD).Enable()
1413+
1414+ # If going from a select snapshot or select release page, record
1415+ # which we were on so the back button of the next page works
1416+ if(self.config.settings['release_or_snapshot'] == "release"):
1417+ self.pages['select_os'].SetNext(self.pages['select_release'])
1418+ self.pages['select_release'].SetPrev(self.pages['select_os'])
1419+
1420+ self.pages['select_release'].SetNext(self.pages['lmc_settings'])
1421+ self.pages['lmc_settings'].SetPrev(self.pages['select_release'])
1422+ else:
1423+ self.pages['select_os'].SetNext(self.pages['select_snapshot'])
1424+ self.pages['select_snapshot'].SetPrev(self.pages['select_os'])
1425+
1426+ if(page == self.pages['select_os']):
1427+ self.pages['select_snapshot'].fill_build_combo_box_for_date(
1428+ self.config.settings['build_date'])
1429+
1430+ self.pages['select_snapshot'].SetNext(self.pages['lmc_settings'])
1431+ self.pages['lmc_settings'].SetPrev(self.pages['select_snapshot'])
1432+
1433+ if page == self.pages['hardware_details']:
1434+ self.pages['select_os'].fill_os_list()
1435+
1436+ if page == self.pages['release_or_snapshot']:
1437+ self.pages['hardware_details'].on_page_changing()
1438+
1439+ # If about to move into the release selection, make sure the list
1440+ # is populated only with releases that are valid with our current
1441+ # selection
1442+ if( page == self.pages['select_os']
1443+ and self.config.settings['release_or_snapshot'] == "release"):
1444+ self.pages['select_release'].update_release_and_build_boxes()
1445+
1446+ if page == self.pages['select_snapshot']:
1447+ # Execute when exiting page
1448+ self.pages['select_snapshot'].update_platform()
1449+
1450+ if( page == self.pages['select_snapshot']
1451+ or page == self.pages['select_release']):
1452+ self.pages['lmc_settings'].on_activate()
1453+
1454+ if page == self.pages['lmc_settings']:
1455+ # Forward stays disabled until LMC has finished running
1456+ self.wizard.FindWindowById(wx.ID_FORWARD).Disable()
1457+ self.pages['run_lmc'].on_activate()
1458+
1459+ else: # Always enable the forward button if reversing into a page
1460+ self.wizard.FindWindowById(wx.ID_FORWARD).Enable()
1461+
1462+ def go(self, first_page):
1463+ file_handler = FetchImage.FileHandler()
1464+ self.config = FetchImage.FetchImageConfig()
1465+ self.config.settings["force_download"] = False
1466+ self.config.settings['compatable_hwpacks'] = ['foo']
1467+
1468+ # If the settings file and server index need updating, grab them
1469+ file_handler.update_files_from_server(show_wx_progress = True)
1470+
1471+ # Load settings YAML, which defines the parameters we ask for and
1472+ # acceptable responses from the user
1473+ self.config.read_config(file_handler.settings_file)
1474+
1475+ # Using the config we have, look up URLs to download data from in
1476+ # the server index
1477+ db = FetchImage.DB(file_handler.index_file)
1478+
1479+ # Create the wizard and the pages
1480+ self.wizard = wiz.Wizard(self, -1, "Linaro Media Builder")
1481+
1482+ self.pages = {}
1483+ self.pages['release_or_snapshot'] = ReleaseOrSnapshotPage(self.wizard,
1484+ self.config)
1485+ self.wizard.FitToPage(self.pages['release_or_snapshot'])
1486+ (width, height) = self.wizard.GetSize()
1487+
1488+ self.pages['hardware_details'] = AboutMyHardwarePage(self.wizard,
1489+ self.config,
1490+ db,
1491+ width)
1492+
1493+ self.pages['select_release'] = SelectStableRelease(self.wizard,
1494+ self.config,
1495+ db,
1496+ width)
1497+
1498+ self.pages['select_snapshot'] = SelectSnapshot(self.wizard,
1499+ self.config,
1500+ db,
1501+ width)
1502+
1503+ self.pages['select_os'] = SelectOS(self.wizard,
1504+ self.config,
1505+ db,
1506+ width)
1507+
1508+ self.pages['lmc_settings'] = LMC_settings(self.wizard,
1509+ self.config,
1510+ db,
1511+ width)
1512+
1513+ self.pages['run_lmc'] = RunLMC(self.wizard,
1514+ self.config,
1515+ db,
1516+ width)
1517+
1518+ self.pages['release_or_snapshot'].SetNext(
1519+ self.pages['hardware_details'])
1520+
1521+ self.pages['hardware_details'].SetPrev(
1522+ self.pages['release_or_snapshot'])
1523+
1524+ self.pages['hardware_details'].SetNext(self.pages['select_os'])
1525+ self.pages['select_os'].SetPrev(self.pages['hardware_details'])
1526+ # Select OS goes to select build, which is customised for
1527+ # releases or snapshots
1528+ self.pages['lmc_settings'].SetNext(self.pages['run_lmc'])
1529+ self.pages['run_lmc'].SetPrev(self.pages['lmc_settings'])
1530+
1531+ for (name, page) in self.pages.items():
1532+ self.wizard.GetPageAreaSizer().Add(page)
1533+
1534+ self.wizard.RunWizard(self.pages['release_or_snapshot'])
1535+
1536+
1537+def run(start_page = None):
1538+ """Wrapper around the full wizard. Is encapsulated in its own function to
1539+ allow a restart to be performed, as described in __main___, easily"""
1540+ app = wx.PySimpleApp() # Start the application
1541+ #logging.basicConfig(level=logging.INFO)
1542+ w = TestDriveWizard('Simple Wizard')
1543+ return w.go(start_page)
1544+
1545+
1546+class TestURLLookupFunctions(unittest.TestCase):
1547+
1548+ def setUp(self):
1549+ self.file_handler = FetchImage.FileHandler()
1550+ self.file_handler.update_files_from_server(show_wx_progress = True)
1551+ self.config = FetchImage.FetchImageConfig()
1552+ self.config.settings["force_download"] = False
1553+
1554+ # Load settings YAML, which defines the parameters we ask for and
1555+ # acceptable responses from the user
1556+ self.config.read_config(self.file_handler.settings_file)
1557+
1558+ # Using the config we have, look up URLs to download data from in the
1559+ # server index
1560+ self.db = FetchImage.DB(self.file_handler.index_file)
1561+
1562+ def test_000_url_lookup_snapshot_builds(self):
1563+ self.settings = self.config.settings
1564+ self.settings['release_or_snapshot'] = "snapshot"
1565+
1566+ #--- Test finding builds near a particular day ---
1567+ # This functionality is required for further tests, hence forcing the
1568+ # run order by putting numbers in the function name.
1569+ today = wx.DateTime()
1570+ today.SetToCurrent()
1571+
1572+ # -- Don't iterate through platforms for snapshot --
1573+
1574+ # -- Select hardware --
1575+ for self.settings['hardware'] in (
1576+ self.settings['choice']['hardware'].keys()):
1577+
1578+ compatable_hwpacks = self.settings['choice']['hwpack'][
1579+ self.settings['hardware']]
1580+
1581+ future_date, past_date = self.db.get_next_prev_day_with_builds(
1582+ "linaro-alip",
1583+ today.FormatISODate().encode('ascii'),
1584+ compatable_hwpacks)
1585+
1586+ if past_date == None:
1587+ # Some hardware packs are not available in the snapshot repo,
1588+ # so just skip if they aren't
1589+ continue
1590+
1591+ builds = self.db.get_binary_builds_on_day_from_db(
1592+ "linaro-alip",
1593+ past_date,
1594+ compatable_hwpacks)
1595+
1596+ self.assertTrue(len(builds))
1597+ # If the above assert fails, either the DB is empty, or
1598+ # db.get_binary_builds_on_day_from_db failed
1599+
1600+ def test_100_url_lookup_snapshots(self):
1601+ self.settings = self.config.settings
1602+ self.settings['release_or_snapshot'] = "snapshot"
1603+
1604+ #--- Test snapshot build lookup ---
1605+ # -- Fix a build date --
1606+ # We only need to look up a single snapshot date. Start with today and
1607+ # go with the day in the DB, build 0
1608+ today = wx.DateTime()
1609+ today.SetToCurrent()
1610+
1611+ # -- Don't iterate through platforms for snapshot --
1612+
1613+ # -- Select hardware --
1614+ for self.settings['hardware'] in (
1615+ self.settings['choice']['hardware'].keys()):
1616+
1617+ compatable_hwpacks = self.settings['choice']['hwpack'][
1618+ self.settings['hardware']]
1619+
1620+ future_date, past_date = self.db.get_next_prev_day_with_builds(
1621+ "linaro-alip",
1622+ today.FormatISODate().encode('ascii'),
1623+ compatable_hwpacks)
1624+
1625+ if past_date == None:
1626+ # Some hardware packs are not available in the snapshot repo,
1627+ # so just skip if they aren't
1628+ continue
1629+
1630+ builds = self.db.get_binary_builds_on_day_from_db(
1631+ "linaro-alip",
1632+ past_date,
1633+ compatable_hwpacks)
1634+
1635+ self.assertTrue(len(builds))
1636+ # The above code is tested in test_000_url_lookup_snapshot_build.
1637+ # If the above assert fails, either the DB is empty, or
1638+ # db.get_binary_builds_on_day_from_db failed.
1639+
1640+ small_date = re.sub('-', '', past_date)
1641+ self.settings['build'] = small_date + ":" + "0"
1642+
1643+ # -- Iterate through hardware packs --
1644+ for self.settings['hwpack'] in compatable_hwpacks:
1645+
1646+ # If hardware pack is available...
1647+ if(self.settings['hwpack']
1648+ in self.db.get_hwpacks('snapshot_hwpacks')):
1649+
1650+ # -- Iterate through images
1651+ os_list = self.db.get_os_list_from('snapshot_binaries')
1652+
1653+ for self.settings['image'] in os_list:
1654+ if re.search('old', self.settings['image']):
1655+ # Directories with old in the name are of no
1656+ # interest to us
1657+ continue
1658+
1659+ # -- Check build which matches these parameters
1660+ # (builds that don't match are excluded in UI) --
1661+ if( len(self.db.execute_return_list(
1662+ 'select * from snapshot_hwpacks '
1663+ 'where hardware == ? '
1664+ 'and date == ? '
1665+ 'and build == ?',
1666+ (self.settings['hwpack'],
1667+ small_date,
1668+ "0")))
1669+ and len(self.db.execute_return_list(
1670+ 'select * from snapshot_binaries '
1671+ 'where image == ? '
1672+ 'and date == ? '
1673+ 'and build == ?',
1674+ (self.settings['image'],
1675+ small_date,
1676+ "0")))):
1677+
1678+ # - Run the function under test! -
1679+ image_url, hwpack_url = (
1680+ self.db.get_image_and_hwpack_urls(self.settings))
1681+
1682+ self.assertTrue(image_url)
1683+ self.assertTrue(hwpack_url)
1684+
1685+ def test_101_url_lookup_releases(self):
1686+ #--- Test release build lookup ---
1687+ self.settings = self.config.settings
1688+ self.settings['release_or_snapshot'] = "release"
1689+ # -- Select hardware --
1690+ for self.settings['hardware'] in (
1691+ self.settings['choice']['hardware'].keys()):
1692+ compatable_hwpacks = (
1693+ self.settings['choice']['hwpack'][self.settings['hardware']])
1694+
1695+ # -- Iterate through hardware packs --
1696+ for self.settings['hwpack'] in compatable_hwpacks:
1697+
1698+ # If hardware pack is available...
1699+ if(self.settings['hwpack']
1700+ in self.db.get_hwpacks('release_hwpacks')):
1701+
1702+ # -- Iterate through images
1703+ os_list = self.db.get_os_list_from('release_binaries')
1704+
1705+ for self.settings['image'] in os_list:
1706+ if re.search('old', self.settings['image']):
1707+ # Directories with old in the name are of no
1708+ # interest to us
1709+ continue
1710+
1711+ for platform, ignore in (
1712+ self.settings['choice']['platform'].items()):
1713+ self.settings['platform'] = platform
1714+
1715+ # -- Iterate through available builds --
1716+ builds = self.db.get_builds(
1717+ self.settings['platform'],
1718+ self.settings['image'])
1719+
1720+ for build in builds:
1721+ self.settings['build'] = build
1722+
1723+ # -- Check build which matches these parameters
1724+ #(builds that don't match are excluded in UI)--
1725+ if( len(self.db.execute_return_list(
1726+ 'select * from release_hwpacks '
1727+ 'where platform == ? '
1728+ 'and hardware == ? '
1729+ 'and build == ?',
1730+ (self.settings['platform'],
1731+ self.settings['hwpack'],
1732+ self.settings['build'])))
1733+ and len(self.db.execute_return_list(
1734+ 'select * from release_binaries '
1735+ 'where platform == ? '
1736+ 'and image == ? '
1737+ 'and build == ?',
1738+ (self.settings['platform'],
1739+ self.settings['image'],
1740+ self.settings['build'])))):
1741+
1742+ # - Run the function under test! -
1743+ image_url, hwpack_url = (
1744+ self.db.get_image_and_hwpack_urls(self.settings))
1745+ self.assertTrue(image_url)
1746+ self.assertTrue(hwpack_url)
1747+
1748+if __name__ == '__main__':
1749+ run()

Subscribers

People subscribed via source and target branches