diff --git a/subiquity/controller.py b/subiquity/controller.py index a3cb73f4..7f6d928d 100644 --- a/subiquity/controller.py +++ b/subiquity/controller.py @@ -28,6 +28,12 @@ class ControllerPolicyException(Exception): class ControllerPolicy: """ Expected contract for defining controllers """ + def __init__(self, common): + self.ui = common['ui'] + self.signal = common['signal'] + self.opts = common['opts'] + self.loop = common['loop'] + self.prober = common['prober'] def register_signals(self): """ Defines signals associated with controller from model """ diff --git a/subiquity/controllers/filesystem.py b/subiquity/controllers/filesystem.py index 9819de37..70a12c91 100644 --- a/subiquity/controllers/filesystem.py +++ b/subiquity/controllers/filesystem.py @@ -14,7 +14,6 @@ # along with this program. If not, see . import logging -import urwid from subiquity.controller import ControllerPolicy from subiquity.models import FilesystemModel from subiquity.ui.views import (DiskPartitionView, AddPartitionView, @@ -30,11 +29,9 @@ BIOS_GRUB_SIZE_BYTES = 2 * 1024 * 1024 # 2MiB class FilesystemController(ControllerPolicy): - def __init__(self, ui, signal, prober): - self.ui = ui - self.signal = signal - self.prober = prober - self.model = FilesystemModel(prober) + def __init__(self, common): + super().__init__(common) + self.model = FilesystemModel(self.prober) def filesystem(self, reset=False): # FIXME: Is this the best way to zero out this list for a reset? @@ -45,7 +42,7 @@ class FilesystemController(ControllerPolicy): title = "Filesystem setup" footer = ("Select available disks to format and mount") self.ui.set_header(title) - self.ui.set_footer(footer) + self.ui.set_footer(footer, 30) self.ui.set_body(FilesystemView(self.model, self.signal)) diff --git a/subiquity/controllers/identity.py b/subiquity/controllers/identity.py index 54842439..86762479 100644 --- a/subiquity/controllers/identity.py +++ b/subiquity/controllers/identity.py @@ -19,9 +19,8 @@ from subiquity.ui.views import IdentityView class IdentityController(ControllerPolicy): - def __init__(self, ui, signal): - self.ui = ui - self.signal = signal + def __init__(self, common): + super().__init__(common) self.model = IdentityModel() def identity(self): diff --git a/subiquity/controllers/installpath.py b/subiquity/controllers/installpath.py index 6c6e7274..c4e54e85 100644 --- a/subiquity/controllers/installpath.py +++ b/subiquity/controllers/installpath.py @@ -23,9 +23,8 @@ log = logging.getLogger('subiquity.controller.installpath') class InstallpathController(ControllerPolicy): - def __init__(self, ui, signal): - self.ui = ui - self.signal = signal + def __init__(self, common): + super().__init__(common) self.model = InstallpathModel() def installpath(self): @@ -38,7 +37,7 @@ class InstallpathController(ControllerPolicy): "navigate options") self.ui.set_header(title, excerpt) - self.ui.set_footer(footer) + self.ui.set_footer(footer, 10) self.ui.set_body(InstallpathView(self.model, self.signal)) def install_ubuntu(self): diff --git a/subiquity/controllers/installprogress.py b/subiquity/controllers/installprogress.py index 11a83184..e3dbc40c 100644 --- a/subiquity/controllers/installprogress.py +++ b/subiquity/controllers/installprogress.py @@ -24,10 +24,8 @@ log = logging.getLogger("subiquity.controller.installprogress") class InstallProgressController(ControllerPolicy): - def __init__(self, ui, signal, opts): - self.ui = ui - self.signal = signal - self.opts = opts + def __init__(self, common): + super().__init__(common) self.model = InstallProgressModel() self.progress_output_w = ProgressOutput(self.signal, "Waiting...") @@ -37,29 +35,31 @@ class InstallProgressController(ControllerPolicy): @coroutine def curtin_dispatch(self): + write_fd = self.loop.watch_pipe(self.install_progress_status) if self.opts.dry_run: log.debug("Install Progress: Curtin dispatch dry-run") yield utils.run_command_async("cat /var/log/syslog", - self.install_progress_status) + write_fd) else: try: yield utils.run_command_async("/usr/local/bin/curtin_wrap.sh", - self.install_progress_status) + write_fd) except: log.error("Problem with curtin dispatch run") raise Exception("Problem with curtin dispatch run") @coroutine def initial_install(self): + write_fd = self.loop.watch_pipe(self.install_progress_status) if self.opts.dry_run: log.debug("Filesystem: this is a dry-run") yield utils.run_command_async("cat /var/log/syslog", - log.debug) + write_fd) else: log.debug("filesystem: this is the *real* thing") yield utils.run_command_async( "/usr/local/bin/curtin_wrap.sh", - log.debug) + write_fd) @coroutine def show_progress(self): @@ -71,13 +71,13 @@ class InstallProgressController(ControllerPolicy): self.ui.set_footer(footer) self.ui.set_body(ProgressView(self.signal, self.progress_output_w)) - if self.opts.dry_run: - banner = [ - "**** DRY_RUN ****", - "NOT calling:" - "subprocess.check_call(/usr/local/bin/curtin_wrap.sh)" - "", - "", - "Press (Q) to Quit." - ] - self.install_progress_status("\n".join(banner)) + # if self.opts.dry_run: + # banner = [ + # "**** DRY_RUN ****", + # "NOT calling:" + # "subprocess.check_call(/usr/local/bin/curtin_wrap.sh)" + # "", + # "", + # "Press (Q) to Quit." + # ] + # self.install_progress_status("\n".join(banner)) diff --git a/subiquity/controllers/network.py b/subiquity/controllers/network.py index 2b4e2452..2efced38 100644 --- a/subiquity/controllers/network.py +++ b/subiquity/controllers/network.py @@ -20,10 +20,8 @@ from subiquity.ui.dummy import DummyView class NetworkController(ControllerPolicy): - def __init__(self, ui, signal, prober): - self.ui = ui - self.signal = signal - self.prober = prober + def __init__(self, common): + super().__init__(common) self.model = NetworkModel(self.prober) def network(self): @@ -33,7 +31,7 @@ class NetworkController(ControllerPolicy): "sufficient access for updates.") footer = ("Additional networking info here") self.ui.set_header(title, excerpt) - self.ui.set_footer(footer) + self.ui.set_footer(footer, 20) self.ui.set_body(NetworkView(self.model, self.signal)) def set_default_route(self): diff --git a/subiquity/controllers/welcome.py b/subiquity/controllers/welcome.py index 884af952..9185d0f2 100644 --- a/subiquity/controllers/welcome.py +++ b/subiquity/controllers/welcome.py @@ -20,10 +20,9 @@ from subiquity.controller import ControllerPolicy class WelcomeController(ControllerPolicy): - def __init__(self, ui, signal): - self.ui = ui + def __init__(self, common): + super().__init__(common) self.model = WelcomeModel() - self.signal = signal def welcome(self): title = "Wilkommen! Bienvenue! Welcome! Zdrastvutie! Welkom!" diff --git a/subiquity/core.py b/subiquity/core.py index 53453540..0fe5f546 100644 --- a/subiquity/core.py +++ b/subiquity/core.py @@ -17,18 +17,11 @@ import logging import urwid import urwid.curses_display from tornado.ioloop import IOLoop +from tornado.util import import_object from subiquity.signals import Signal from subiquity.palette import STYLES, STYLES_MONO from subiquity.prober import Prober -# Modes import ---------------------------------------------------------------- -from subiquity.controllers import (WelcomeController, - InstallpathController, - NetworkController, - FilesystemController, - IdentityController, - InstallProgressController) - log = logging.getLogger('subiquity.core') @@ -39,21 +32,21 @@ class CoreControllerError(Exception): class Controller: def __init__(self, ui, opts): - self.ui = ui - self.opts = opts - self.signal = Signal() - self.prober = Prober(self.opts) - self.controllers = { - "welcome": WelcomeController(self.ui, self.signal), - "installpath": InstallpathController(self.ui, self.signal), - "network": NetworkController(self.ui, self.signal, self.prober), - "filesystem": FilesystemController(self.ui, self.signal, - self.prober), - "identity": IdentityController(self.ui, self.signal), - "progress": InstallProgressController(self.ui, self.signal, - self.opts) + self.common = { + "ui": ui, + "opts": opts, + "signal": Signal(), + "prober": Prober(opts), + "loop": None + } + self.controllers = { + "Welcome": None, + "Installpath": None, + "Network": None, + "Filesystem": None, + "Identity": None, + "InstallProgress": None, } - self._connect_base_signals() def _connect_base_signals(self): """ Connect signals used in the core controller @@ -63,23 +56,23 @@ class Controller: # Add quit signal signals.append(('quit', self.exit)) signals.append(('refresh', self.redraw_screen)) - self.signal.connect_signals(signals) + self.common['signal'].connect_signals(signals) # Registers signals from each controller for controller, controller_class in self.controllers.items(): controller_class.register_signals() - log.debug(self.signal) + log.debug(self.common['signal']) # EventLoop ------------------------------------------------------------------- def redraw_screen(self): if hasattr(self, 'loop'): try: - self.loop.draw_screen() + self.common['loop'].draw_screen() except AssertionError as e: log.critical("Redraw screen error: {}".format(e)) def set_alarm_in(self, interval, cb): - self.loop.set_alarm_in(interval, cb) + self.common['loop'].set_alarm_in(interval, cb) return def update(self, *args, **kwds): @@ -101,7 +94,7 @@ class Controller: 'unhandled_input': self.header_hotkeys, 'handle_mouse': False } - if self.opts.run_on_serial: + if self.common['opts'].run_on_serial: palette = STYLES_MONO additional_opts['screen'] = urwid.curses_display.Screen() else: @@ -109,15 +102,22 @@ class Controller: additional_opts['screen'].reset_default_terminal_palette() evl = urwid.TornadoEventLoop(IOLoop()) - self.loop = urwid.MainLoop( - self.ui, palette, event_loop=evl, **additional_opts) - log.debug("Running event loop: {}".format(self.loop.event_loop)) + self.common['loop'] = urwid.MainLoop( + self.common['ui'], palette, event_loop=evl, **additional_opts) + log.debug("Running event loop: {}".format( + self.common['loop'].event_loop)) try: self.set_alarm_in(0.05, self.welcome) - # self.install_progress_fd = self.loop.watch_pipe( - # self.install_progress_status) - self.loop.run() + for k in self.controllers.keys(): + log.debug("Importing controller: {}".format(k)) + klass = import_object( + "subiquity.controllers.{}Controller".format( + k)) + self.controllers[k] = klass(self.common) + + self._connect_base_signals() + self.common['loop'].run() except: log.exception("Exception in controller.run():") raise @@ -126,4 +126,4 @@ class Controller: # # Starts the initial UI view. def welcome(self, *args, **kwargs): - self.controllers['welcome'].welcome() + self.controllers['Welcome'].welcome() diff --git a/subiquity/palette.py b/subiquity/palette.py index 62a43fd9..bcc2e357 100644 --- a/subiquity/palette.py +++ b/subiquity/palette.py @@ -56,7 +56,11 @@ STYLES = [ ('string_input', '', '', '', Palette.black, Palette.light_gray), ('string_input focus', '', '', '', - Palette.white, Palette.dark_gray) + Palette.white, Palette.dark_gray), + ('progress_incomplete', '', '', '', + Palette.white, Palette.dark_magenta), + ('progress_complete', '', '', '', + Palette.white, Palette.light_magenta) ] @@ -75,4 +79,12 @@ STYLES_MONO = [('frame_header', Palette.white, Palette.black, ('button', Palette.white, Palette.black, '', '', ''), ('button focus', Palette.white, Palette.black, - '', '', '')] + '', '', ''), + ('string_input', '', '', '', + Palette.white, ''), + ('string_input focus', '', '', '', + Palette.white, ''), + ('progress_incomplete', '', '', '', + '', Palette.black), + ('progress_complete', '', '', '', + '', Palette.white)] diff --git a/subiquity/ui/anchors.py b/subiquity/ui/anchors.py index 3f27dd2e..363d6fcf 100644 --- a/subiquity/ui/anchors.py +++ b/subiquity/ui/anchors.py @@ -13,7 +13,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from urwid import WidgetWrap, Pile, Text +from urwid import WidgetWrap, Pile, Text, ProgressBar +from collections import deque from subiquity.ui.utils import Padding, Color from subiquity.ui.lists import SimpleList @@ -47,10 +48,19 @@ class Footer(WidgetWrap): """ - def __init__(self, message=""): + def __init__(self, message="", completion=0): message_widget = Padding.center_79(Color.body(Text(message))) - status = Pile([Padding.line_break(""), message_widget]) - super().__init__(status) + progress_bar = Padding.center_60( + ProgressBar(normal='progress_incomplete', + complete='progress_complete', + current=completion, done=100)) + status = deque([ + Padding.line_break(""), + message_widget + ]) + if completion > 0: + status.appendleft(progress_bar) + super().__init__(Pile(status)) class Body(WidgetWrap): diff --git a/subiquity/ui/frame.py b/subiquity/ui/frame.py index 8e2d0289..21680f07 100644 --- a/subiquity/ui/frame.py +++ b/subiquity/ui/frame.py @@ -40,8 +40,8 @@ class SubiquityUI(WidgetWrap): def set_header(self, title=None, excerpt=None): self.frame.header = Header(title, excerpt) - def set_footer(self, message): - self.frame.footer = Footer(message) + def set_footer(self, message, completion=0): + self.frame.footer = Footer(message, completion) def set_body(self, widget): self.frame.body = widget diff --git a/subiquity/utils.py b/subiquity/utils.py index 22cfc6cf..075bf9fb 100644 --- a/subiquity/utils.py +++ b/subiquity/utils.py @@ -13,11 +13,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import errno import subprocess -import os -import codecs -import pty from subiquity.async import Async import shlex import logging @@ -35,60 +31,7 @@ def run_command(cmd, streaming_callback=None): if isinstance(cmd, str): cmd = shlex.split(cmd) log.debug("Running command: {}".format(cmd)) - stdoutm, stdouts = pty.openpty() - proc = subprocess.Popen(cmd, - stdout=stdouts, - stderr=subprocess.PIPE) - os.close(stdouts) - decoder = codecs.getincrementaldecoder('utf-8')() - - def last_ten_lines(s): - chunk = s[-1500:] - lines = chunk.splitlines(True) - return ''.join(lines[-10:]).replace('\r', '') - - decoded_output = "" - try: - while proc.poll() is None: - try: - b = os.read(stdoutm, 512) - except OSError as e: - if e.errno != errno.EIO: - raise - break - else: - final = False - if not b: - final = True - decoded_chars = decoder.decode(b, final) - if decoded_chars is None: - continue - - decoded_output += decoded_chars - if streaming_callback: - ls = last_ten_lines(decoded_output) - - streaming_callback(ls) - if final: - break - finally: - os.close(stdoutm) - if proc.poll() is None: - proc.kill() - proc.wait() - - errors = [l.decode('utf-8') for l in proc.stderr.readlines()] - if streaming_callback: - streaming_callback(last_ten_lines(decoded_output)) - - errors = ''.join(errors) - - if proc.returncode == 0: - return decoded_output.strip() - else: - log.debug("Error with command: " - "[Output] '{}' [Error] '{}'".format( - decoded_output.strip(), - errors.strip())) - raise Exception("Problem running command: [Error] '{}'".format( - errors.strip())) + proc = subprocess.Popen(cmd, close_fds=True, + stdout=streaming_callback) + proc.kill() + # streaming_callback.close()