From patchwork Thu Jul 14 13:59:12 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Tunnicliffe X-Patchwork-Id: 2691 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id AFF2024259 for ; Thu, 14 Jul 2011 13:59:16 +0000 (UTC) Received: from mail-qy0-f180.google.com (mail-qy0-f180.google.com [209.85.216.180]) by fiordland.canonical.com (Postfix) with ESMTP id 32CE6A18126 for ; Thu, 14 Jul 2011 13:59:16 +0000 (UTC) Received: by qyk30 with SMTP id 30so235434qyk.11 for ; Thu, 14 Jul 2011 06:59:15 -0700 (PDT) Received: by 10.229.68.200 with SMTP id w8mr1837491qci.114.1310651955608; Thu, 14 Jul 2011 06:59:15 -0700 (PDT) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.229.217.78 with SMTP id hl14cs15989qcb; Thu, 14 Jul 2011 06:59:14 -0700 (PDT) Received: by 10.216.78.198 with SMTP id g48mr2238095wee.45.1310651953515; Thu, 14 Jul 2011 06:59:13 -0700 (PDT) Received: from adelie.canonical.com (adelie.canonical.com [91.189.90.139]) by mx.google.com with ESMTP id p71si626174weq.6.2011.07.14.06.59.12; Thu, 14 Jul 2011 06:59:13 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.139 as permitted sender) client-ip=91.189.90.139; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.139 as permitted sender) smtp.mail=bounces@canonical.com Received: from loganberry.canonical.com ([91.189.90.37]) by adelie.canonical.com with esmtp (Exim 4.71 #1 (Debian)) id 1QhMRc-0004Sc-P2 for ; Thu, 14 Jul 2011 13:59:12 +0000 Received: from loganberry.canonical.com (localhost [127.0.0.1]) by loganberry.canonical.com (Postfix) with ESMTP id B859E2E80F1 for ; Thu, 14 Jul 2011 13:59:12 +0000 (UTC) MIME-Version: 1.0 X-Launchpad-Project: linaro-image-tools X-Launchpad-Branch: ~linaro-image-tools/linaro-image-tools/trunk X-Launchpad-Message-Rationale: Subscriber X-Launchpad-Branch-Revision-Number: 376 X-Launchpad-Notification-Type: branch-revision To: Linaro Patch Tracker From: noreply@launchpad.net Subject: [Branch ~linaro-image-tools/linaro-image-tools/trunk] Rev 376: Removed GUI code from the FetchImage library. Message-Id: <20110714135912.32754.55299.launchpad@loganberry.canonical.com> Date: Thu, 14 Jul 2011 13:59:12 -0000 Reply-To: noreply@launchpad.net Sender: bounces@canonical.com Errors-To: bounces@canonical.com Precedence: bulk X-Generated-By: Launchpad (canonical.com); Revision="13405"; Instance="initZopeless config overlay" X-Launchpad-Hash: 38e77825d2689f128306da57c8ad7467e9de577e Merge authors: James Tunnicliffe (dooferlad) Related merge proposals: https://code.launchpad.net/~dooferlad/linaro-image-tools/remove_gui_from_FetchImage_library/+merge/67950 proposed by: James Tunnicliffe (dooferlad) review: Approve - James Westby (james-w) ------------------------------------------------------------ revno: 376 [merge] committer: James Tunnicliffe branch nick: linaro-image-tools timestamp: Thu 2011-07-14 14:57:40 +0100 message: Removed GUI code from the FetchImage library. Updated the download status the UI gives to the user, so now it is integrated into the wizard. modified: fetch_image_ui.py linaro_image_tools/FetchImage.py --- lp:linaro-image-tools https://code.launchpad.net/~linaro-image-tools/linaro-image-tools/trunk You are subscribed to branch lp:linaro-image-tools. To unsubscribe from this branch go to https://code.launchpad.net/~linaro-image-tools/linaro-image-tools/trunk/+edit-subscription === modified file 'fetch_image_ui.py' --- fetch_image_ui.py 2011-06-17 18:29:14 +0000 +++ fetch_image_ui.py 2011-07-14 12:50:56 +0000 @@ -31,6 +31,8 @@ import unittest import operator import Queue +import time +import datetime def add_button(bind_to, @@ -43,9 +45,9 @@ """Create a radio button with event bindings.""" if(style != None): - radio_button = wx.RadioButton(bind_to, label = label, style = style) + radio_button = wx.RadioButton(bind_to, label=label, style=style) else: - radio_button = wx.RadioButton(bind_to, label = label) + radio_button = wx.RadioButton(bind_to, label=label) sizer.Add(radio_button, 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5) bind_to.Bind(wx.EVT_RADIOBUTTON, select_event, radio_button) @@ -126,9 +128,9 @@ self.box2 = wx.BoxSizer(wx.VERTICAL) header = wx.StaticText(self, - label = "Please select the hardware that you " - "would like to build an image for from " - "the following list") + label="Please select the hardware that you " + "would like to build an image for from " + "the following list") header.Wrap(width - 10) # -10 because boarder below is 5 pixels wide @@ -143,11 +145,12 @@ self.settings['hardware'] = default_hardware self.settings['compatable_hwpacks'] = ( self.settings['choice']['hwpack'][self.settings['hardware']]) - + self.cb_hardware = wx.ComboBox(self, - value = - self.settings['choice']['hardware'][default_hardware], - style = wx.CB_DROPDOWN | wx.CB_READONLY) + value=self.settings['choice'] + ['hardware'] + [default_hardware], + style=wx.CB_DROPDOWN | wx.CB_READONLY) self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_hardware, @@ -201,8 +204,8 @@ self.sizer = wx.BoxSizer(wx.VERTICAL) self.wizard = parent - header = wx.StaticText(self, label = "Please select the stable Linaro " - "release you would like to use") + header = wx.StaticText(self, label="Please select the stable Linaro " + "release you would like to use") header.Wrap(width - 10) # -10 because boarder below is 5 pixels wide @@ -215,8 +218,8 @@ default_release = self.settings['UI']['translate'][platforms[-1]] self.cb_release = wx.ComboBox(self, - value = default_release, - style = wx.CB_DROPDOWN | wx.CB_READONLY) + value=default_release, + style=wx.CB_DROPDOWN | wx.CB_READONLY) self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_release, self.cb_release) @@ -234,12 +237,12 @@ self.cb_release.Append(item, item.upper()) self.cb_build = wx.ComboBox(self, - style = wx.CB_DROPDOWN | wx.CB_READONLY) + style=wx.CB_DROPDOWN | wx.CB_READONLY) self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_build, self.cb_build) self.box1.Add(self.cb_release, 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5) - self.box1.Add(self.cb_build, 0, + self.box1.Add(self.cb_build, 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5) self.sizer.Add(self.box1, 0, wx.ALIGN_LEFT | wx.ALL, 5) self.SetSizerAndFit(self.sizer) @@ -255,7 +258,7 @@ self.cb_build.SetValue("No build available") for build in builds: - if( self.db.hardware_is_available_for_platform_build( + if(self.db.hardware_is_available_for_platform_build( self.settings['compatable_hwpacks'], self.settings['platform'], build) @@ -336,12 +339,12 @@ self.sizer = wx.BoxSizer(wx.VERTICAL) header = wx.StaticText(self, - label = "Builds are created most days. First " - "please select the day on which the " - "build you would like to use was built," - " then, if there was more than one " - "build that day you will be able to " - "select the build number.") + label="Builds are created most days. First " + "please select the day on which the " + "build you would like to use was built," + " then, if there was more than one " + "build that day you will be able to " + "select the build number.") header.Wrap(width - 10) # -10 because boarder below is 5 pixels wide box1 = wx.BoxSizer(wx.VERTICAL) @@ -353,8 +356,8 @@ self.today.SetToCurrent() self.settings['build_date'] = self.today.FormatISODate().encode('ascii') - dpc = wx.DatePickerCtrl(self, size = (120, -1), - style = wx.DP_DEFAULT) + dpc = wx.DatePickerCtrl(self, size=(120, -1), + style=wx.DP_DEFAULT) self.Bind(wx.EVT_DATE_CHANGED, self.on_date_changed, dpc) #--- Build number Combo Box --- @@ -363,7 +366,7 @@ self.settings['build_number'] = 0 self.update_build() self.cb_build = wx.ComboBox(self, - style = wx.CB_DROPDOWN | wx.CB_READONLY) + style=wx.CB_DROPDOWN | wx.CB_READONLY) self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_build, self.cb_build) #--- Layout --- @@ -493,16 +496,16 @@ self.sizer = wx.BoxSizer(wx.VERTICAL) self.settings['image'] = None - header = wx.StaticText(self, label = "Please select the operating " - "system you would like to run on " - "your hardware.") + header = wx.StaticText(self, label="Please select the operating " + "system you would like to run on " + "your hardware.") header.Wrap(width - 10) # -10 because boarder below is 5 pixels wide self.box1 = wx.BoxSizer(wx.VERTICAL) self.sizer.Add(header) self.cb_image = wx.ComboBox(self, - style = wx.CB_DROPDOWN | wx.CB_READONLY) + style=wx.CB_DROPDOWN | wx.CB_READONLY) self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_os, self.cb_image) #--- Layout --- @@ -679,7 +682,7 @@ self.settings['path_selected'] = "" header = wx.StaticText(self, - label = "Media Creation Settings\n\n" + label="Media Creation Settings\n\n" "Please select if you would like to write the " "file system I am about to create to a memory " "card, or to a file on the local file system.") @@ -691,8 +694,8 @@ default_target = file_systems[0] self.settings['rootfs'] = default_target cb_rootfs = wx.ComboBox(self, - value = default_target, - style = wx.CB_DROPDOWN | wx.CB_READONLY) + value=default_target, + style=wx.CB_DROPDOWN | wx.CB_READONLY) for item in file_systems: cb_rootfs.Append(item, item.upper()) @@ -737,14 +740,14 @@ self.help_text_values = {"device": "Please select a device to write " "the file system to:", + "file": "Please select a file to write the " "file system to:"} self.help_text = wx.StaticText( self, - label = - self.help_text_values[ - self.settings['write_to_file_or_device']]) + label=self.help_text_values[ + self.settings['write_to_file_or_device']]) self.help_text.Wrap(width - 10) #-- File/dev picker -- @@ -774,7 +777,7 @@ #-- Combo boxes for hardware and image selection -- optional_settings_box_title = wx.StaticBox( self, - label = " Optional Settings ") + label=" Optional Settings ") self.optional_settings_box = wx.StaticBoxSizer( optional_settings_box_title, @@ -790,7 +793,7 @@ grid1.Add(cb_rootfs, 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5) grid1.Add(wx.StaticText(self, - label = "The root file system of the image"), + label="The root file system of the image"), 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5) @@ -811,14 +814,14 @@ # Add a choice of MB or GB for size input units = ["GB", "MB"] self.size_unit = units[0] # Set the default unit - unit_choice = wx.Choice(self, -1, (100, 50), choices = units) + unit_choice = wx.Choice(self, -1, (100, 50), choices=units) self.Bind(wx.EVT_CHOICE, self.event_chose_unit, unit_choice) file_size_grid.Add(unit_choice, 0, wx.ALIGN_RIGHT | wx.TOP, 5) # Back out of the extra grid, add some help text grid1.Add(wx.StaticText( self, - label = "Writing to file only: Image file size"), + label="Writing to file only: Image file size"), 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5) @@ -829,15 +832,15 @@ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5) - grid1.Add(wx.StaticText(self, label = "Swap file size in MB"), + grid1.Add(wx.StaticText(self, label="Swap file size in MB"), 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5) self.cb_hwpacks = wx.ComboBox( self, - value = self.settings['compatable_hwpacks'][0], - style = wx.CB_DROPDOWN | wx.CB_READONLY) + value=self.settings['compatable_hwpacks'][0], + style=wx.CB_DROPDOWN | wx.CB_READONLY) self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_hwpack, @@ -848,14 +851,14 @@ wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5) - grid1.Add(wx.StaticText(self, label = "Compatible hardware packs"), + grid1.Add(wx.StaticText(self, label="Compatible hardware packs"), 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5) self.optional_settings_box.Add(grid1, 0, wx.ALIGN_LEFT | wx.ALL, 5) - confirm_mmc_usage_title = wx.StaticBox(self, label = " Are you sure? ") + confirm_mmc_usage_title = wx.StaticBox(self, label=" Are you sure? ") self.confirm_mmc_usage_box = wx.StaticBoxSizer(confirm_mmc_usage_title, wx.VERTICAL) @@ -1031,7 +1034,7 @@ self.width = width self.wizard = parent - header = wx.StaticText(self, label = """Installing...""") + header = wx.StaticText(self, label="""Installing...""") header.Wrap(width - 10) # -10 because boarder below is 5 pixels wide self.sizer.Add(header) @@ -1039,7 +1042,7 @@ # We expect to print 4 lines of information, reserve space using blank # lines. - self.settings_summary_text = wx.StaticText(self, label = "\n\n\n\n") + self.settings_summary_text = wx.StaticText(self, label="\n\n\n\n") self.settings_summary_text.Wrap(width - 10) self.box1.Add(self.settings_summary_text, 0, wx.ALIGN_LEFT | wx.ALL, 5) @@ -1053,7 +1056,12 @@ self.start_button.SetSize(self.start_button.GetBestSize()) self.box1.Add(self.start_button, 0, wx.ALIGN_LEFT | wx.ALL, 5) - self.status_grid = wx.FlexGridSizer(0, 2, 0, 0) + self.download_guage = wx.Gauge(self, + -1, + 1000, + size=(self.width * 2 / 3, 25)) + + self.status_grid = wx.FlexGridSizer(0, 2) self.status_grid.Add(wx.StaticText(self, label="Downloading files"), 0, @@ -1061,11 +1069,24 @@ 5) self.downloading_files_status = wx.StaticText(self, label="") + self.status_grid.Add(self.downloading_files_status, 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5) + self.status_grid.Add(self.download_guage, + 0, + wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, + 5) + + self.downloading_files_info = wx.StaticText(self, label="") + + self.status_grid.Add(self.downloading_files_info, + 0, + wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, + 5) + self.status_grid.Add(wx.StaticText(self, label="Unpacking downloads"), 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, @@ -1147,7 +1168,7 @@ settings_summary += ( "Writing image to " + self.settings['write_to_file_or_device'] - + " " + + " " + path) self.settings_summary_text.SetLabel(settings_summary) @@ -1175,29 +1196,26 @@ if(image_url and hwpack_url): - print image_url - print hwpack_url - self.file_handler = FetchImage.FileHandler() + self.timer = wx.Timer(self) + self.Bind(wx.EVT_TIMER, self.timer_ping, self.timer) + self.timer.Start(milliseconds=100, oneShot=True) + tools_dir = os.path.dirname(__file__) if tools_dir == '': tools_dir = None - self.file_handler.create_media(image_url, - hwpack_url, - self.settings, - tools_dir, - True, - self) - - self.timer = wx.Timer(self) - self.Bind(wx.EVT_TIMER, self.timer_ping, self.timer) - self.timer.Start(milliseconds=100, oneShot=True) - self.start_button.Disable() self.event_queue = Queue.Queue() - self.file_handler.start_lmc_gui_thread(self.event_queue) + self.lmc_thread = self.file_handler.LinaroMediaCreate( + image_url, + hwpack_url, + self.file_handler, + self.event_queue, + self.settings, + tools_dir) + self.lmc_thread.start() else: print >> sys.stderr, ("Unable to find files that match the" "parameters specified") @@ -1207,27 +1225,28 @@ linaro-media-create every 100ms. This is the function which is called to do that polling.""" - if self.event_queue.empty() == False: + while not self.event_queue.empty(): event = self.event_queue.get() if event[0] == "start": self.event_start(event[1]) - self.timer.Start(milliseconds=100, oneShot=True) elif event[0] == "end": self.event_end(event[1]) - self.timer.Start(milliseconds=100, oneShot=True) elif event == "terminate": # Process complete. Enable next button. self.wizard.FindWindowById(wx.ID_FORWARD).Enable() self.populate_file_system_status.SetLabel("Done") + return # Even if queue isn't empty, stop processing it + + elif event[0] == "update": + self.event_update(event[1], event[2], event[3]) else: print >> sys.stderr, "timer_ping: Unhandled event", event - else: - self.timer.Start(milliseconds=100, oneShot=True) + self.timer.Start(milliseconds=50, oneShot=True) def unsigned_packages_query(self, package_list): message = ('In order to continue, I need to install some unsigned' @@ -1264,7 +1283,7 @@ self.file_handler.kill_create_media() sys.exit(1) else: - self.file_handler.send_to_create_process("y") + self.lmc_thread.send_to_create_process("y") elif event == "create file system": self.create_file_system_status.SetLabel("Running") @@ -1289,6 +1308,79 @@ else: print "Unhhandled end event:", event + def event_update(self, task, update_type, value): + if task == "download": + if update_type == "name": + self.downloading_files_status.SetLabel("Downloading") + self.old_time = time.time() + self.old_bytes_downloaded = 0 + + elif update_type == "progress": + self.total_bytes_downloaded += value + + time_difference = time.time() - self.old_time + + if time_difference > 1.0: + self.old_time = time.time() + + # More than a second has passed since we calculated data + # rate + speed = ( float( self.total_bytes_downloaded + - self.old_bytes_downloaded) + / time_difference) + + self.old_bytes_downloaded = self.total_bytes_downloaded + + self.speeds.append(speed) + + average_speed = 0 + speeds_accumulated = 0 + for speed in reversed(self.speeds): + average_speed += speed + speeds_accumulated += 1 + + if speeds_accumulated == 6: + break # do rolling average of 6 seconds + + average_speed /= speeds_accumulated + + time_remaining = ( ( self.total_bytes_to_download + - self.total_bytes_downloaded) + / speed) + + pretty_time = str(datetime.timedelta(seconds=int( + time_remaining))) + + # Following table assumes we don't get past TBps internet + # connections soon :-) + units = ["Bps", "kBps", "MBps", "GBps", "TBps"] + units_index = 0 + while speed > 1024: + speed /= 1024 + units_index += 1 + + info = "Downloading at {0:.1f} {1}".format( + speed, + units[units_index]) + + self.downloading_files_status.SetLabel(info) + + info = "{0} remaining".format( + pretty_time) + + self.downloading_files_info.SetLabel(info) + + self.download_guage.SetValue( 1000 + * self.total_bytes_downloaded + / self.total_bytes_to_download) + + elif update_type == "total bytes": + self.total_bytes_to_download = value + self.total_bytes_downloaded = 0 + self.speeds = [] # keep an array of speeds used to calculate + # the estimated time remaining - by not just using the + # current speed we can stop the ETA bouncing around too much. + def event_combo_box_release(self, evt): pass @@ -1371,14 +1463,14 @@ else: # Always enable the forward button if reversing into a page self.wizard.FindWindowById(wx.ID_FORWARD).Enable() - def go(self, first_page): + def go(self): file_handler = FetchImage.FileHandler() self.config = FetchImage.FetchImageConfig() self.config.settings["force_download"] = False self.config.settings['compatable_hwpacks'] = ['foo'] # If the settings file and server index need updating, grab them - file_handler.update_files_from_server(show_wx_progress = True) + file_handler.update_files_from_server() # Load settings YAML, which defines the parameters we ask for and # acceptable responses from the user @@ -1446,20 +1538,22 @@ self.wizard.RunWizard(self.pages['release_or_snapshot']) -def run(start_page = None): +def run(): """Wrapper around the full wizard. Is encapsulated in its own function to allow a restart to be performed, as described in __main___, easily""" app = wx.PySimpleApp() # Start the application - #logging.basicConfig(level=logging.INFO) + if app: + pass # We don't use this directly. Stop pyflakes complaining! + w = TestDriveWizard('Simple Wizard') - return w.go(start_page) + return w.go() class TestURLLookupFunctions(unittest.TestCase): def setUp(self): self.file_handler = FetchImage.FileHandler() - self.file_handler.update_files_from_server(show_wx_progress = True) + self.file_handler.update_files_from_server() self.config = FetchImage.FetchImageConfig() self.config.settings["force_download"] = False === modified file 'linaro_image_tools/FetchImage.py' --- linaro_image_tools/FetchImage.py 2011-06-17 18:04:47 +0000 +++ linaro_image_tools/FetchImage.py 2011-07-14 12:50:56 +0000 @@ -52,15 +52,6 @@ "image-tools", "fetch_image") - class DummyEventHandler(): - """Just a sink for events if no event handler is provided to - create_media""" - def event_start(self, event): - pass - - def event_end(self, event): - pass - def has_key_and_evaluates_True(self, dictionary, key): return bool(key in dictionary and dictionary[key]) @@ -72,17 +63,15 @@ list.append(setting_name) list.append(dictionary[key]) - def create_media(self, image_url, hwpack_url, settings, tools_dir, - run_in_gui=False, event_handler=None): - """Create a command line for linaro-media-create based on the settings - provided then run linaro-media-create, either in a separate thread - (GUI mode) or in the current one (CLI mode).""" + def build_lmc_command(self, + binary_file, + hwpack_file, + settings, + tools_dir, + run_in_gui=False): import linaro_image_tools.utils - if event_handler == None: - event_handler = self.DummyEventHandler() - args = [] args.append("pkexec") @@ -100,41 +89,6 @@ if run_in_gui: args.append("--nocheck-mmc") - event_handler.event_start("download OS") - try: - binary_file = self.download(image_url, - settings["force_download"], - show_wx_progress=run_in_gui, - wx_progress_title= - "Downloading file 1 of 2") - except Exception: - # Download error. Hardly matters what, we can't continue. - print "Unexpected error:", sys.exc_info()[0] - logging.error("Unable to download " + image_url + " - aborting.") - event_handler.event_end("download OS") - - if binary_file == None: # User hit cancel when downloading - sys.exit(0) - - event_handler.event_start("download hwpack") - try: - hwpack_file = self.download(hwpack_url, - settings["force_download"], - show_wx_progress=run_in_gui, - wx_progress_title= - "Downloading file 2 of 2") - except Exception: - # Download error. Hardly matters what, we can't continue. - print "Unexpected error:", sys.exc_info()[0] - logging.error("Unable to download " + hwpack_url + " - aborting.") - event_handler.event_end("download hwpack") - - if hwpack_file == None: # User hit cancel when downloading - sys.exit(0) - - logging.info("Have downloaded OS binary to", binary_file, - "and hardware pack to", hwpack_file) - if 'rootfs' in settings and settings['rootfs']: args.append("--rootfs") args.append(settings['rootfs']) @@ -157,34 +111,74 @@ args.append("--hwpack") args.append(hwpack_file) - logging.info(args) - - if run_in_gui: - self.lmcargs = args - self.event_handler = event_handler - self.started_lmc = False - return - - else: - self.create_process = subprocess.Popen(args) - self.create_process.wait() + logging.info(" ".join(args)) + + return args + + def create_media(self, image_url, hwpack_url, settings, tools_dir): + """Create a command line for linaro-media-create based on the settings + provided then run linaro-media-create, either in a separate thread + (GUI mode) or in the current one (CLI mode).""" + + to_download = [(image_url, "OS"), + (hwpack_url, "hwpack")] + downloaded_files = self.download_files(to_download, + settings) + + args = self.build_lmc_command(downloaded_files['OS'], + downloaded_files['hwpack'], + settings, + tools_dir) + + self.create_process = subprocess.Popen(args) + self.create_process.wait() class LinaroMediaCreate(threading.Thread): """Thread class for running linaro-media-create""" - def __init__(self, event_handler, lmcargs, event_queue): + def __init__(self, + image_url, + hwpack_url, + file_handler, + event_queue, + settings, + tools_dir): + threading.Thread.__init__(self) - self.event_handler = event_handler - self.lmcargs = lmcargs - self.event_queue = event_queue + + self.image_url = image_url + self.hwpack_url = hwpack_url + self.file_handler = file_handler + self.event_queue = event_queue + self.settings = settings + self.tools_dir = tools_dir def run(self): - """Start linaro-media-create and look for lines in the output that: - 1. Tell us that an event has happened that we can use to update the - UI progress. - 2. Tell us that linaro-media-create is asking a question that needs - to be re-directed to the GUI""" - - self.create_process = subprocess.Popen(self.lmcargs, + """ + 1. Download required files. + 2. Build linaro-media-create command + 3. Start linaro-media-create and look for lines in the output that: + 1. Tell us that an event has happened that we can use to update + the UI progress. + 2. Tell us that linaro-media-create is asking a question that + needs to be re-directed to the GUI + """ + + to_download = [(self.image_url, "OS"), + (self.hwpack_url, "hwpack")] + + downloaded_files = self.file_handler.download_files( + to_download, + self.settings, + self.event_queue) + + lmc_command = self.file_handler.build_lmc_command( + downloaded_files['OS'], + downloaded_files['hwpack'], + self.settings, + self.tools_dir, + True) + + self.create_process = subprocess.Popen(lmc_command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) @@ -262,18 +256,6 @@ print >> self.create_process.stdin, text self.waiting_for_event_response = False - def start_lmc_gui_thread(self, event_queue): - self.lmc_thread = self.LinaroMediaCreate(self.event_handler, - self.lmcargs, event_queue) - self.lmc_thread.start() - - def kill_create_media(self): - pass # TODO: Something! - # Need to make sure all child processes are terminated. - - def send_to_create_process(self, text): - self.lmc_thread.send_to_create_process(text) - def name_and_path_from_url(self, url): """Return the file name and the path at which the file will be stored on the local system based on the URL we are downloading from""" @@ -290,34 +272,94 @@ return file_name, file_path - def create_wx_progress(self, title, message): - """Create a standard WX progrss dialog""" - import wx - self.dlg = wx.ProgressDialog(title, - message, - maximum=1000, - parent=None, - style=wx.PD_CAN_ABORT - | wx.PD_APP_MODAL - | wx.PD_ELAPSED_TIME - | wx.PD_AUTO_HIDE - | wx.PD_REMAINING_TIME) - - def timer_ping(self): - self.update_wx_process(self.download_count) - - def update_wx_progress(self, count): - self.download_count = count - (self.do_download, skip) = self.dlg.Update(count) - - def download(self, url, force_download=False, - show_wx_progress=False, wx_progress_title=None): + def urllib2_open(self, url): + maxtries = 10 + for trycount in range(0, maxtries): + try: + response = urllib2.urlopen(url) + except: + if trycount < maxtries - 1: + print "Unable to connect to", url, "retrying in 5 seconds..." + time.sleep(5) + continue + else: + print "Connect failed:", url + raise + return None + else: + return response + + return None + + def download_files(self, + downloads_list, + settings, + event_queue=None): + """ + Download files specified in the downloads_list, which is a list of + url, name tuples. + """ + + downloaded_files = {} + + bytes_to_download = 0 + + for url, name in downloads_list: + file_name, file_path = self.name_and_path_from_url(url) + + file_name = file_path + os.sep + file_name + if os.path.exists(file_name): + continue # If file already exists, don't download it + + response = self.urllib2_open(url) + if response: + bytes_to_download += int(response.info() + .getheader('Content-Length').strip()) + response.close() + + if event_queue: + event_queue.put(("update", + "download", + "total bytes", + bytes_to_download)) + + for url, name in downloads_list: + if event_queue: + event_queue.put(("start", "download " + name)) + event_queue.put(("update", + "download", + "name", + name)) + + path = None + try: + path = self.download(url, + event_queue, + settings["force_download"]) + except Exception: + # Download error. Hardly matters what, we can't continue. + print "Unexpected error:", sys.exc_info()[0] + logging.error("Unable to download " + url + " - aborting.") + + if event_queue: + event_queue.put(("end", "download " + name)) + + if path == None: # User hit cancel when downloading + sys.exit(0) + + downloaded_files[name] = path + logging.info("Have downloaded {0} to {1}".format(name, path)) + + return downloaded_files + + def download(self, + url, + event_queue, + force_download=False): """Downloads the file requested buy URL to the local cache and returns the full path to the downloaded file""" file_name, file_path = self.name_and_path_from_url(url) - - just_file_name = file_name file_name = file_path + os.sep + file_name if not os.path.isdir(file_path): @@ -328,23 +370,9 @@ "--force-download to override).") return file_name - logging.info("Fetching", url) + logging.info("Fetching " + url) - maxtries = 10 - for trycount in range(0, maxtries): - try: - response = urllib2.urlopen(url) - except: - if trycount < maxtries - 1: - print "Unable to download", url, "retrying in 5 seconds..." - time.sleep(5) - continue - else: - print "Download failed for some reason:", url - raise - return - else: - break + response = self.urllib2_open(url) self.do_download = True file_out = open(file_name, 'w') @@ -356,28 +384,22 @@ if show_progress: chunk_size = download_size_in_bytes / 1000 - if show_wx_progress: - if wx_progress_title == None: - wx_progress_title = "Downloading File" - self.create_wx_progress(wx_progress_title, - "Downloading " + just_file_name) - else: + if not event_queue: print "Fetching", url else: chunk_size = download_size_in_bytes - if show_progress and show_wx_progress: - # Just update the download box before we get the first % - self.update_wx_progress(0) - printed_progress = False while self.do_download: chunk = response.read(chunk_size) if len(chunk): # Print a % download complete so we don't get too bored if show_progress: - if show_wx_progress: - self.update_wx_progress(chunks_downloaded) + if event_queue: + event_queue.put(("update", + "download", + "progress", + len(chunk))) else: # Have 1000 chunks so div by 10 to get %... sys.stdout.write("\r%d%%" % (chunks_downloaded / 10)) @@ -400,7 +422,7 @@ return file_name - def download_if_old(self, url, force_download, show_wx_progress=False): + def download_if_old(self, url, event_queue, force_download): file_name, file_path = self.name_and_path_from_url(url) file_path_and_name = file_path + os.sep + file_name @@ -411,25 +433,25 @@ force_download = (force_download == True or ( time.mktime(time.localtime()) - os.path.getmtime(file_path_and_name) - > 60 * 60 * 24)) + > 60 * 60)) except OSError: force_download = True # File not found... - return self.download(url, force_download, show_wx_progress) + return self.download(url, event_queue, force_download) def update_files_from_server(self, force_download=False, - show_wx_progress=False): + event_queue=None): - settings_url = "http://z.nanosheep.org/fetch_image_settings.yaml" - server_index_url = "http://z.nanosheep.org/server_index.bz2" + settings_url = "http://releases.linaro.org/fetch_image/fetch_image_settings.yaml" + server_index_url = "http://releases.linaro.org/fetch_image/server_index.bz2" self.settings_file = self.download_if_old(settings_url, - force_download, - show_wx_progress) + event_queue, + force_download) self.index_file = self.download_if_old(server_index_url, - force_download, - show_wx_progress) + event_queue, + force_download) zip_search = re.search(r"^(.*)\.bz2$", self.index_file) @@ -484,9 +506,9 @@ self.settings['UI']['reverse-translate'][value] = key def parse_args(self, args): - parser = argparse.ArgumentParser(description= - "Create a board image, first " - "downloading any required files.") + parser = argparse.ArgumentParser(description="Create a board image, " + "first downloading any required " + "files.") for (key, value) in self.settings['choice'].items(): parser.add_argument( @@ -983,7 +1005,7 @@ loop_date_increment = -one_day test_date[in_the] = current_date - + while test_date[in_the] <= max_search_date: test_date[in_the] += loop_date_increment