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:
Adam Stokes 2015-08-31 11:55:46 -04:00
parent cd14fb2bdb
commit d149a0c014
12 changed files with 106 additions and 143 deletions

View File

@ -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 """

View File

@ -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))

View File

@ -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):

View File

@ -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):

View File

@ -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))

View File

@ -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):

View File

@ -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!"

View File

@ -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()

View File

@ -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)]

View File

@ -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):

View File

@ -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

View File

@ -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()