Additional UI notifications and consolidate common attrs
We now pass a common dictionary with attributes needed throughout our controllers. The common dictionary now contains: - eventloop - ui widgets - signal handler - prober options - cli arguments In addition we've extended the progress indicator to include a progressbar. Signed-off-by: Adam Stokes <adam.stokes@ubuntu.com>
This commit is contained in:
parent
cd14fb2bdb
commit
d149a0c014
|
@ -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 """
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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))
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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!"
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -13,11 +13,7 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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()
|
||||
|
|
Loading…
Reference in New Issue