From 709ae2a51adbe99b8160358c9add6a0f1f24e004 Mon Sep 17 00:00:00 2001 From: Adam Stokes Date: Sun, 23 Aug 2015 23:55:45 -0400 Subject: [PATCH] Update install progress controller Provide streaming updates to the UI during non blocking commands. For the progress controller specifically this is the output from curtin_wrap and subsequent curtin execs. Signed-off-by: Adam Stokes --- subiquity/controllers/installprogress.py | 50 +++++++++++++++++++----- subiquity/core.py | 4 +- subiquity/models/__init__.py | 11 +++--- subiquity/models/installprogress.py | 47 ++++++++++++++++++++++ subiquity/ui/views/identity.py | 2 +- subiquity/ui/views/installprogress.py | 21 +++++----- 6 files changed, 108 insertions(+), 27 deletions(-) create mode 100644 subiquity/models/installprogress.py diff --git a/subiquity/controllers/installprogress.py b/subiquity/controllers/installprogress.py index d0f34ae0..9d45b1f7 100644 --- a/subiquity/controllers/installprogress.py +++ b/subiquity/controllers/installprogress.py @@ -14,7 +14,9 @@ # along with this program. If not, see . import logging -import subprocess +from tornado.gen import coroutine +import subiquity.utils as utils +from subiquity.models import InstallProgressModel from subiquity.ui.views import ProgressView, ProgressOutput from subiquity.controller import ControllerPolicy @@ -22,9 +24,39 @@ log = logging.getLogger("subiquity.controller.installprogress") class InstallProgressController(ControllerPolicy): - def __init__(self, ui, signal): + def __init__(self, ui, signal, opts): self.ui = ui self.signal = signal + self.opts = opts + self.model = InstallProgressModel() + self.progress_output_w = ProgressOutput( + self.signal, + "Waiting...") + + def exit_cb(self, ret): + log.debug("Exit: {}".format(ret)) + + @coroutine + def run_curtin(self): + try: + yield utils.run_command_async( + "/usr/local/bin/curtin_wrap.sh", + self.install_progress_status) + except Exception as e: + # TODO: Implement an Error View/Controller for displaying + # exceptions rather than kicking out of installer. + log.error("Problem running curtin_wrap: {}".format(e)) + + @coroutine + def run_test_curtin(self): + """ testing streaming output + """ + self.install_progress_status("Starting run") + yield utils.run_command_async( + "cat /var/log/syslog", + self.install_progress_status) + log.debug("done") + return def install_progress(self): title = ("Installing system") @@ -33,6 +65,7 @@ class InstallProgressController(ControllerPolicy): footer = ("Thank you for using Ubuntu!") self.ui.set_header(title, excerpt) self.ui.set_footer(footer) + self.ui.set_body(ProgressView(self.signal, self.progress_output_w)) if self.opts.dry_run: log.debug("Filesystem: this is a dry-run") banner = [ @@ -43,15 +76,14 @@ class InstallProgressController(ControllerPolicy): "", "Press (Q) to Quit." ] - self.progress_output_w = ProgressOutput("\n".join(banner)) + self.install_progress_status("\n".join(banner)) + # XXX: Test routine to verify the callback streaming + # self.run_test_curtin() else: log.debug("filesystem: this is the *real* thing") - subprocess.Popen(["/usr/local/bin/curtin_wrap.sh"], - stdout=self.install_progress_fd, - bufsize=1, - universal_newlines=True) - self.progress_output_w = ProgressOutput("Wait for it...\n\n") - self.ui.set_body(ProgressView(self.signal, self.progress_output_w)) + self.run_curtin() def install_progress_status(self, data): + log.debug("Running status output: {}".format(data)) self.progress_output_w.set_text(data) + self.signal.emit_signal('refresh') diff --git a/subiquity/core.py b/subiquity/core.py index f717dfc6..2f066650 100644 --- a/subiquity/core.py +++ b/subiquity/core.py @@ -47,7 +47,8 @@ class Controller: "network": NetworkController(self.ui, self.signal), "filesystem": FilesystemController(self.ui, self.signal), "identity": IdentityController(self.ui, self.signal), - "progress": InstallProgressController(self.ui, self.signal), + "progress": InstallProgressController(self.ui, self.signal, + self.opts) } self._connect_base_signals() @@ -58,6 +59,7 @@ class Controller: # Add quit signal signals.append(('quit', self.exit)) + signals.append(('refresh', self.redraw_screen)) self.signal.connect_signals(signals) # Registers signals from each controller diff --git a/subiquity/models/__init__.py b/subiquity/models/__init__.py index 15dff962..223c472c 100644 --- a/subiquity/models/__init__.py +++ b/subiquity/models/__init__.py @@ -13,8 +13,9 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from .filesystem import FilesystemModel -from .installpath import InstallpathModel -from .network import NetworkModel -from .welcome import WelcomeModel -from .identity import IdentityModel +from .filesystem import FilesystemModel # NOQA +from .installpath import InstallpathModel # NOQA +from .network import NetworkModel # NOQA +from .welcome import WelcomeModel # NOQA +from .identity import IdentityModel # NOQA +from .installprogress import InstallProgressModel # NOQA diff --git a/subiquity/models/installprogress.py b/subiquity/models/installprogress.py new file mode 100644 index 00000000..8b1256f5 --- /dev/null +++ b/subiquity/models/installprogress.py @@ -0,0 +1,47 @@ +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import logging +from subiquity.model import ModelPolicy + + +log = logging.getLogger('subiquity.models.installprogress') + + +class InstallProgressModel(ModelPolicy): + """ Model representing install progress + """ + # FIXME: Decide what to do here if ESC is pressed, it's probably in + # a state of no return so may be better to just exit with error. + prev_signal = None + + signals = [ + ("Installprogress view", + 'installprogress:show', + 'install_progress') + ] + + installprogress_menu = [] + + def get_signals(self): + return self.signals + + def get_menu(self): + return self.installprogress_menu + + def get_signal_by_name(self, selection): + for x, y, z in self.get_menu(): + if x == selection: + return y diff --git a/subiquity/ui/views/identity.py b/subiquity/ui/views/identity.py index 52de0c12..1876117e 100644 --- a/subiquity/ui/views/identity.py +++ b/subiquity/ui/views/identity.py @@ -73,7 +73,7 @@ class IdentityView(ViewPolicy): "confirm_password": self.confirm_password.value } log.debug("User input: {}".format(result)) - # emit_signal(self.signal, 'installpath:show') + emit_signal(self.signal, 'installprogress:show') def cancel(self, button): self.signal.emit_signal("quit") diff --git a/subiquity/ui/views/installprogress.py b/subiquity/ui/views/installprogress.py index 8fd82d88..ce472eb4 100644 --- a/subiquity/ui/views/installprogress.py +++ b/subiquity/ui/views/installprogress.py @@ -13,27 +13,25 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from urwid import (Text, Filler, WidgetWrap, +import logging +from urwid import (Text, Filler, ListBox, BoxAdapter) from subiquity.view import ViewPolicy from subiquity.ui.utils import Color, Padding +log = logging.getLogger("subiquity.ui.views.installprogress") -class ProgressOutput(WidgetWrap): - def __init__(self, txt): + +class ProgressOutput(ViewPolicy): + def __init__(self, signal, txt): + self.signal = signal self.txt = Text(txt) flr = Filler(Color.info_minor(self.txt), - valign="bottom") + valign="top") super().__init__(BoxAdapter(flr, height=20)) - def split_text(self): - return self.txt.text.splitlines() - def set_text(self, data): - data = data.decode("utf8") - lines = self.split_text() + data.splitlines() - out = "\n".join(lines[-20:]) - self.txt.set_text(out) + self.txt.set_text(data) class ProgressView(ViewPolicy): @@ -41,6 +39,7 @@ class ProgressView(ViewPolicy): """ :param output_w: Filler widget to display updated status text """ + self.signal = signal self.body = [ Padding.center_79(output_w) ]