From f429372d3c143b1bdf4741d7893ae699306ecd37 Mon Sep 17 00:00:00 2001 From: Adam Stokes Date: Tue, 21 Jul 2015 11:55:02 -0400 Subject: [PATCH] refactor controller code and signal handling Signed-off-by: Adam Stokes --- bin/subiquity | 2 +- subiquity/controllers/__init__.py | 113 -------- subiquity/controllers/filesystem.py | 120 --------- subiquity/controllers/installpath.py | 49 ---- subiquity/controllers/network.py | 47 ---- subiquity/controllers/policy.py | 45 ---- subiquity/controllers/welcome.py | 46 ---- subiquity/core.py | 253 ++++++++++++++++++ .../filesystem.py => filesystem/__init__.py} | 101 ++++++- subiquity/{models => filesystem}/actions.py | 0 subiquity/{models => filesystem}/blockdev.py | 2 +- .../__init__.py} | 28 +- subiquity/models/__init__.py | 68 ----- subiquity/models/filesystem.py | 117 -------- subiquity/models/identity.py | 31 --- subiquity/models/installpath.py | 34 --- subiquity/models/network.py | 103 ------- subiquity/models/welcome.py | 33 --- .../{views/network.py => network/__init__.py} | 86 +++++- subiquity/routes.py | 81 ------ subiquity/signals.py | 26 +- subiquity/ui/frame.py | 3 +- subiquity/views/__init__.py | 14 - .../{views/welcome.py => welcome/__init__.py} | 29 +- 24 files changed, 500 insertions(+), 931 deletions(-) delete mode 100644 subiquity/controllers/__init__.py delete mode 100644 subiquity/controllers/filesystem.py delete mode 100644 subiquity/controllers/installpath.py delete mode 100644 subiquity/controllers/network.py delete mode 100644 subiquity/controllers/policy.py delete mode 100644 subiquity/controllers/welcome.py create mode 100644 subiquity/core.py rename subiquity/{views/filesystem.py => filesystem/__init__.py} (74%) rename subiquity/{models => filesystem}/actions.py (100%) rename subiquity/{models => filesystem}/blockdev.py (99%) rename subiquity/{views/installpath.py => installpath/__init__.py} (74%) delete mode 100644 subiquity/models/__init__.py delete mode 100644 subiquity/models/filesystem.py delete mode 100644 subiquity/models/identity.py delete mode 100644 subiquity/models/installpath.py delete mode 100644 subiquity/models/network.py delete mode 100644 subiquity/models/welcome.py rename subiquity/{views/network.py => network/__init__.py} (55%) delete mode 100644 subiquity/routes.py delete mode 100644 subiquity/views/__init__.py rename subiquity/{views/welcome.py => welcome/__init__.py} (73%) diff --git a/bin/subiquity b/bin/subiquity index e3e06732..0550f531 100755 --- a/bin/subiquity +++ b/bin/subiquity @@ -19,7 +19,7 @@ import sys import logging from subiquity.log import setup_logger from subiquity import __version__ as VERSION -from subiquity.controllers import BaseController as Subiquity +from subiquity.core import Controller as Subiquity from subiquity.ui.frame import SubiquityUI diff --git a/subiquity/controllers/__init__.py b/subiquity/controllers/__init__.py deleted file mode 100644 index 05e4713e..00000000 --- a/subiquity/controllers/__init__.py +++ /dev/null @@ -1,113 +0,0 @@ -# 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 -import urwid -import urwid.curses_display -from subiquity.routes import Routes -from subiquity.palette import STYLES, STYLES_MONO - - -log = logging.getLogger('subiquity.controller') - - -class BaseControllerError(Exception): - """ Basecontroller exception """ - pass - - -class BaseController: - def __init__(self, ui, opts): - self.ui = ui - self.opts = opts - - def next_controller(self, *args, **kwds): - controller = Routes.next() - controller(self).show(*args, **kwds) - - def prev_controller(self, *args, **kwds): - controller = Routes.prev() - controller(self).show(*args, **kwds) - - def current_controller(self, *args, **kwds): - controller = Routes.current() - return controller(self) - - def redraw_screen(self): - if hasattr(self, 'loop'): - try: - self.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) - return - - def update(self, *args, **kwds): - """ Update loop """ - pass - - def exit(self): - raise urwid.ExitMainLoop() - - def header_hotkeys(self, key): - if key in ['esc'] and Routes.current_idx() != 0: - self.prev_controller() - if key in ['q', 'Q', 'ctrl c']: - self.exit() - - def set_body(self, w): - self.ui.set_body(w) - self.redraw_screen() - - def set_header(self, title=None, excerpt=None): - self.ui.set_header(title, excerpt) - self.redraw_screen() - - def set_footer(self, message): - self.ui.set_footer(message) - self.redraw_screen() - - def run(self): - if not hasattr(self, 'loop'): - palette = STYLES - additional_opts = { - 'screen': urwid.raw_display.Screen(), - 'unhandled_input': self.header_hotkeys, - 'handle_mouse': False - } - if self.opts.run_on_serial: - palette = STYLES_MONO - additional_opts['screen'] = urwid.curses_display.Screen() - else: - additional_opts['screen'].set_terminal_properties(colors=256) - additional_opts['screen'].reset_default_terminal_palette() - - self.loop = urwid.MainLoop( - self.ui, palette, **additional_opts) - - try: - self.set_alarm_in(0.05, self.begin) - self.loop.run() - except: - log.exception("Exception in controller.run():") - raise - - def begin(self, *args, **kwargs): - """ Initializes the first controller for installation """ - Routes.reset() - initial_controller = Routes.first() - initial_controller(self).show() diff --git a/subiquity/controllers/filesystem.py b/subiquity/controllers/filesystem.py deleted file mode 100644 index 1f92ffe2..00000000 --- a/subiquity/controllers/filesystem.py +++ /dev/null @@ -1,120 +0,0 @@ -# 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 . - -from subiquity.views.filesystem import (FilesystemView, - DiskPartitionView, - AddPartitionView) -from subiquity.models.filesystem import FilesystemModel -from subiquity.curtin import curtin_write_storage_actions - -from urwid import connect_signal -import logging -import subprocess - - -log = logging.getLogger('subiquity.filesystemController') - - -class FilesystemController: - """ Filesystem Controller """ - fs_model = FilesystemModel() - - def __init__(self, ui): - self.ui = ui - - # Filesystem actions - def show(self, *args, **kwds): - title = "Filesystem setup" - footer = ("Select available disks to format and mount") - - fs_view = FilesystemView(self.fs_model) - connect_signal(fs_view, 'fs:done', self.finish) - connect_signal(fs_view, 'fs:dp:view', self.show_disk_partition_view) - - self.ui.set_header(title) - self.ui.set_footer(footer) - self.ui.set_body(fs_view) - return - - def finish(self, reset=False, actions=None): - """ - :param bool reset: Reset model options - :param actions: storage actions - - Signal: - key: 'fs:done' - usage: emit_signal(self, 'fs:done', (reset, actions)) - """ - if actions is None and reset is False: - return self.ui.prev_controller() - - log.info("Rendering curtin config from user choices") - curtin_write_storage_actions(actions=actions) - if self.ui.opts.dry_run: - log.debug("filesystem: this is a dry-run") - print("\033c") - print("**** DRY_RUN ****") - print('NOT calling: ' - 'subprocess.check_call("/usr/local/bin/curtin_wrap.sh")') - print("**** DRY_RUN ****") - else: - log.debug("filesystem: this is the *real* thing") - print("\033c") - print("**** Calling curtin installer ****") - subprocess.check_call("/usr/local/bin/curtin_wrap.sh") - - return self.ui.exit() - - # DISK Partitioning actions - def show_disk_partition_view(self, disk): - log.debug("In disk partition view, using {} as the disk.".format(disk)) - title = ("Paritition, format, and mount {}".format(disk)) - footer = ("Paritition the disk, or format the entire device " - "without partitions.") - self.ui.set_header(title) - self.ui.set_footer(footer) - dp_view = DiskPartitionView(self.fs_model, - disk) - - connect_signal(dp_view, 'fs:dp:done', self.finish_disk_paritition_view) - connect_signal(dp_view, 'fs:show-add-partition', - self.show_add_disk_partition_view) - - self.ui.set_body(dp_view) - return - - def finish_disk_paritition_view(self, result): - log.debug("Finish disk-p-v: {}".format(result)) - return self.ui.exit() - - # ADD Partitioning actions - def show_add_disk_partition_view(self, disk): - adp_view = AddPartitionView(self.fs_model, - disk) - connect_signal(adp_view, - 'fs:add-partition:done', - self.finish_add_disk_paritition_view) - self.ui.set_body(adp_view) - return - - def finish_add_disk_partition_view(self, partition_spec): - if not partition_spec: - log.debug("New partition: {}".format(partition_spec)) - else: - log.debug("Empty partition spec, should go back one.") - return self.ui.exit() - - -__controller_class__ = FilesystemController diff --git a/subiquity/controllers/installpath.py b/subiquity/controllers/installpath.py deleted file mode 100644 index c98c9cd6..00000000 --- a/subiquity/controllers/installpath.py +++ /dev/null @@ -1,49 +0,0 @@ -# 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 . - -from subiquity.controllers.policy import ControllerPolicy -from subiquity.views.installpath import InstallpathView -from subiquity.models.installpath import InstallpathModel -import logging - -log = logging.getLogger('subiquity.installpath') - - -class InstallpathController(ControllerPolicy): - """InstallpathController""" - - title = "15.10" - excerpt = ("Welcome to Ubuntu! The world's favourite platform " - "for clouds, clusters and amazing internet things. " - "This is the installer for Ubuntu on servers and " - "internet devices.") - footer = ("Use UP, DOWN arrow keys, and ENTER, to " - "navigate options") - - def show(self, *args, **kwds): - log.debug("Loading install path controller") - self.ui.set_header(self.title, self.excerpt) - self.ui.set_footer(self.footer) - model = InstallpathModel() - self.ui.set_body(InstallpathView(model, self.finish)) - return - - def finish(self, install_selection=None): - log.debug("installpath cb selection: {}".format(install_selection)) - if install_selection is None: - return self.ui.prev_controller() - return self.ui.next_controller() - -__controller_class__ = InstallpathController diff --git a/subiquity/controllers/network.py b/subiquity/controllers/network.py deleted file mode 100644 index ee8e90e3..00000000 --- a/subiquity/controllers/network.py +++ /dev/null @@ -1,47 +0,0 @@ -# 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 . - -from subiquity.controllers.policy import ControllerPolicy -from subiquity.views.network import NetworkView -from subiquity.models.network import NetworkModel -import logging - -log = logging.getLogger('subiquity.network') - - -class NetworkController(ControllerPolicy): - """InstallpathController""" - - title = "Network connections" - excerpt = ("Configure at least the main interface this server will " - "use to talk to other machines, and preferably provide " - "sufficient access for updates.") - - footer = ("Additional networking info here") - - def show(self, *args, **kwds): - self.model = NetworkModel() - self.ui.set_header(self.title, self.excerpt) - self.ui.set_footer(self.footer) - self.ui.set_body(NetworkView(self.model, self.finish)) - return - - def finish(self, interface=None): - if interface is None: - return self.ui.prev_controller() - log.info("Network Interface choosen: {}".format(interface)) - return self.ui.next_controller() - -__controller_class__ = NetworkController diff --git a/subiquity/controllers/policy.py b/subiquity/controllers/policy.py deleted file mode 100644 index b60fa68e..00000000 --- a/subiquity/controllers/policy.py +++ /dev/null @@ -1,45 +0,0 @@ -# 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 . - -""" Controller policy """ - -from abc import ABCMeta, abstractmethod - - -class ControllerPolicy(metaclass=ABCMeta): - """ Policy class for controller specifics - """ - signals = ('done', 'cancel', 'reset') - - def __init__(self, ui): - self.ui = ui - - @abstractmethod - def show(self, *args, **kwds): - """ Implements show action for the controller - - This is the entrypoint for all initial controller - views. - """ - pass - - @abstractmethod - def finish(self): - """ Implements finish action for controller. - - This handles any callback data/procedures required - to move to the next controller or end the install. - """ - pass diff --git a/subiquity/controllers/welcome.py b/subiquity/controllers/welcome.py deleted file mode 100644 index 8e1d2119..00000000 --- a/subiquity/controllers/welcome.py +++ /dev/null @@ -1,46 +0,0 @@ -# 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 . - -from subiquity.controllers.policy import ControllerPolicy -from subiquity.views.welcome import WelcomeView -from subiquity.models.welcome import WelcomeModel -import logging - - -log = logging.getLogger('subiquity.controllers.welcome') - - -class WelcomeController(ControllerPolicy): - """WelcomeController""" - title = "Wilkommen! Bienvenue! Welcome! Zdrastvutie! Welkom!" - excerpt = "Please choose your preferred language" - footer = ("Use UP, DOWN arrow keys, and ENTER, to " - "select your language.") - - def show(self, *args, **kwds): - self.ui.set_header(self.title, self.excerpt) - self.ui.set_footer(self.footer) - self.ui.set_body(WelcomeView(WelcomeModel, self.finish)) - return - - def finish(self, language=None): - if language is None: - raise SystemExit("No language selected, exiting as there are no " - "more previous controllers to render.") - WelcomeModel.selected_language = language - log.debug("Welcome Model: {}".format(WelcomeModel())) - return self.ui.next_controller() - -__controller_class__ = WelcomeController diff --git a/subiquity/core.py b/subiquity/core.py new file mode 100644 index 00000000..7558f4b0 --- /dev/null +++ b/subiquity/core.py @@ -0,0 +1,253 @@ +# 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 +import urwid +import urwid.curses_display +import subprocess +from subiquity.signals import Signal +from subiquity.palette import STYLES, STYLES_MONO +from subiquity.curtin import curtin_write_storage_actions + +# Modes import ---------------------------------------------------------------- +from subiquity.welcome import WelcomeView, WelcomeModel +from subiquity.network import NetworkView, NetworkModel +from subiquity.installpath import InstallpathView, InstallpathModel +from subiquity.filesystem import (FilesystemView, + DiskPartitionView, + AddPartitionView, + FilesystemModel) + +log = logging.getLogger('subiquity.core') + + +class CoreControllerError(Exception): + """ Basecontroller exception """ + pass + + +class Controller: + def __init__(self, ui, opts): + self.ui = ui + self.opts = opts + self.signal = Signal() + self.signal.register_signals() + +# EventLoop ------------------------------------------------------------------- + def redraw_screen(self): + if hasattr(self, 'loop'): + try: + self.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) + return + + def update(self, *args, **kwds): + """ Update loop """ + pass + + def exit(self): + raise urwid.ExitMainLoop() + + def header_hotkeys(self, key): + if key in ['q', 'Q', 'ctrl c']: + self.exit() + + def run(self): + if not hasattr(self, 'loop'): + palette = STYLES + additional_opts = { + 'screen': urwid.raw_display.Screen(), + 'unhandled_input': self.header_hotkeys, + 'handle_mouse': False + } + if self.opts.run_on_serial: + palette = STYLES_MONO + additional_opts['screen'] = urwid.curses_display.Screen() + else: + additional_opts['screen'].set_terminal_properties(colors=256) + additional_opts['screen'].reset_default_terminal_palette() + + self.loop = urwid.MainLoop( + self.ui, palette, **additional_opts) + + try: + self.set_alarm_in(0.05, self.welcome) + self.loop.run() + except: + log.exception("Exception in controller.run():") + raise + +# Base UI Actions ------------------------------------------------------------- + def set_body(self, w): + self.ui.set_body(w) + self.redraw_screen() + + def set_header(self, title=None, excerpt=None): + self.ui.set_header(title, excerpt) + self.redraw_screen() + + def set_footer(self, message): + self.ui.set_footer(message) + self.redraw_screen() + + +# Modes ---------------------------------------------------------------------- + + # Welcome ----------------------------------------------------------------- + def welcome(self, *args, **kwargs): + title = "Wilkommen! Bienvenue! Welcome! Zdrastvutie! Welkom!" + excerpt = "Please choose your preferred language" + footer = ("Use UP, DOWN arrow keys, and ENTER, to " + "select your language.") + self.ui.set_header(title, excerpt) + self.ui.set_footer(footer) + model = WelcomeModel() + view = WelcomeView(model, self.signal) + urwid.connect_signal(self.signal, 'welcome:finish', + self.welcome_handler) + urwid.connect_signal(self.signal, 'installpath:show', self.installpath) + self.ui.set_body(view) + self.welcome_model = WelcomeModel() + + def welcome_handler(self, language=None): + log.debug("Welcome handler") + if language is None: + raise SystemExit("No language selected, exiting as there are no " + "more previous controllers to render.") + self.welcome_model.selected_language = language + log.debug("Welcome Model: {}".format(self.welcome_model)) + urwid.emit_signal(self.signal, 'installpath:show') + + # InstallPath ------------------------------------------------------------- + def installpath(self): + title = "15.10" + excerpt = ("Welcome to Ubuntu! The world's favourite platform " + "for clouds, clusters and amazing internet things. " + "This is the installer for Ubuntu on servers and " + "internet devices.") + footer = ("Use UP, DOWN arrow keys, and ENTER, to " + "navigate options") + + self.ui.set_header(title, excerpt) + self.ui.set_footer(footer) + self.installpath_model = InstallpathModel() + urwid.connect_signal(self.signal, 'installpath:finish', + self.installpath_handler) + urwid.connect_signal(self.signal, 'welcome:show', self.welcome) + self.ui.set_body(InstallpathView(self.installpath_model, self.signal)) + + def installpath_handler(self, install_selection=None): + if install_selection is None: + urwid.emit_signal(self.signal, 'welcome:show', []) + urwid.emit_signal(self.signal, 'network:show', []) + + # Network ----------------------------------------------------------------- + def network(self): + title = "Network connections" + excerpt = ("Configure at least the main interface this server will " + "use to talk to other machines, and preferably provide " + "sufficient access for updates.") + footer = ("Additional networking info here") + self.network_model = NetworkModel() + self.ui.set_header(title, excerpt) + self.ui.set_footer(footer) + urwid.connect_signal(self.signal, 'network:finish', + self.network_handler) + urwid.connect_signal(self.signal, 'installpath:show', self.installpath) + urwid.connect_signal(self.signal, 'filesystem:show', self.filesystem) + self.ui.set_body(NetworkView(self.network_model)) + + def network_handler(self, interface=None): + log.info("Network Interface choosen: {}".format(interface)) + if interface is None: + urwid.emit_signal(self.signal, 'installpath:show', []) + urwid.emit_signal(self.signal, 'filesystem:show', []) + + # Filesystem -------------------------------------------------------------- + def filesystem(self): + title = "Filesystem setup" + footer = ("Select available disks to format and mount") + self.ui.set_header(title) + self.ui.set_footer(footer) + + self.fs_model = FilesystemModel() + urwid.connect_signal(self.signal, 'filesystem:finish', + self.filesystem_handler) + urwid.connect_signal(self.signal, 'filesystem:show-disk-partition', + self.disk_partition) + self.ui.set_body(FilesystemView(self.fs_model)) + + def filesystem_handler(self, reset=False, actions=None): + if actions is None and reset is False: + urwid.emit_signal(self.signal, 'network:show', []) + + log.info("Rendering curtin config from user choices") + curtin_write_storage_actions(actions=actions) + if self.opts.dry_run: + log.debug("filesystem: this is a dry-run") + print("\033c") + print("**** DRY_RUN ****") + print('NOT calling: ' + 'subprocess.check_call("/usr/local/bin/curtin_wrap.sh")') + print("**** DRY_RUN ****") + else: + log.debug("filesystem: this is the *real* thing") + print("\033c") + print("**** Calling curtin installer ****") + subprocess.check_call("/usr/local/bin/curtin_wrap.sh") + return self.ui.exit() + + # Filesystem/Disk partition ----------------------------------------------- + def disk_partition(self, disk): + log.debug("In disk partition view, using {} as the disk.".format(disk)) + title = ("Paritition, format, and mount {}".format(disk)) + footer = ("Paritition the disk, or format the entire device " + "without partitions.") + self.ui.set_header(title) + self.ui.set_footer(footer) + dp_view = DiskPartitionView(self.fs_model, + disk) + + urwid.connect_signal(self.signal, 'filesystem:finish-disk-partition', + self.disk_paritition_handler) + urwid.connect_signal(self.signal, 'filesystem:add-disk-partition', + self.add_disk_partition) + + self.ui.set_body(dp_view) + + def disk_partition_handler(self, spec=None): + log.debug("Disk partition: {}".format(spec)) + if spec is None: + urwid.emit_signal(self.signal, 'filesystem:show', []) + urwid.emit_signal(self.signal, 'filesystem:show-disk-partition', []) + + def add_disk_partition(self, disk): + adp_view = AddPartitionView(self.fs_model, + disk) + urwid.connect_signal(self.signal, + 'filesystem:finish-add-disk-partition', + self.add_disk_paritition_handler) + self.ui.set_body(adp_view) + + def add_disk_partition_handler(self, partition_spec): + self.exit() + if not partition_spec: + log.debug("New partition: {}".format(partition_spec)) + else: + log.debug("Empty partition spec, should go back one.") diff --git a/subiquity/views/filesystem.py b/subiquity/filesystem/__init__.py similarity index 74% rename from subiquity/views/filesystem.py rename to subiquity/filesystem/__init__.py index 3959301b..6076b540 100644 --- a/subiquity/views/filesystem.py +++ b/subiquity/filesystem/__init__.py @@ -13,17 +13,114 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +""" Filesystem + +Provides storage device selection and additional storage +configuration. + +""" import logging +import json +import argparse + +from subiquity.filesystem.blockdev import Blockdev +from probert import prober +from probert.storage import StorageInfo import math from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter, Text, Columns, LineBox, Edit, RadioButton) from subiquity.ui.lists import SimpleList from subiquity.ui.buttons import done_btn, reset_btn, cancel_btn from subiquity.ui.utils import Padding, Color -from subiquity.signals import emit_signal + +log = logging.getLogger('subiquity.filesystem') -log = logging.getLogger('subiquity.filesystemView') +class FilesystemModel: + """ Model representing storage options + """ + + fs_menu = [ + 'Connect iSCSI network disk', + 'Connect Ceph network disk', + 'Create volume group (LVM2)', + 'Create software RAID (MD)', + 'Setup hierarchichal storage (bcache)' + ] + + partition_menu = [ + 'Add first GPT partition', + 'Format or create swap on entire device (unusual, advanced)' + ] + + supported_filesystems = [ + 'ext4', + 'xfs', + 'btrfs', + 'swap', + 'bcache cache', + 'bcache store', + 'leave unformatted' + ] + + def __init__(self): + self.storage = {} + self.info = {} + self.devices = {} + self.options = argparse.Namespace(probe_storage=True, + probe_network=False) + self.prober = prober.Prober(self.options) + self.probe_storage() + + def probe_storage(self): + self.prober.probe() + self.storage = self.prober.get_results().get('storage') + log.debug('storage probe data:\n{}'.format( + json.dumps(self.storage, indent=4, sort_keys=True))) + + # TODO: replace this with Storage.get_device_by_match() + # which takes a lambda fn for matching + VALID_MAJORS = ['8', '253'] + for disk in self.storage.keys(): + if self.storage[disk]['DEVTYPE'] == 'disk' and \ + self.storage[disk]['MAJOR'] in VALID_MAJORS: + log.debug('disk={}\n{}'.format(disk, + json.dumps(self.storage[disk], indent=4, + sort_keys=True))) + self.info[disk] = StorageInfo({disk: self.storage[disk]}) + + def get_disk(self, disk): + if disk not in self.devices: + self.devices[disk] = Blockdev(disk, self.info[disk].serial) + return self.devices[disk] + + def get_partitions(self): + partitions = [] + for dev in self.devices.values(): + partnames = [part.path for part in dev.disk.partitions] + partitions += partnames + + sorted(partitions) + return partitions + + def get_available_disks(self): + return sorted(self.info.keys()) + + def get_used_disks(self): + return [dev.disk.path for dev in self.devices.values() + if dev.available is False] + + def get_disk_info(self, disk): + return self.info[disk] + + def get_disk_action(self, disk): + return self.devices[disk].get_actions() + + def get_actions(self): + actions = [] + for dev in self.devices.values(): + actions += dev.get_actions() + return actions def _humanize_size(size): diff --git a/subiquity/models/actions.py b/subiquity/filesystem/actions.py similarity index 100% rename from subiquity/models/actions.py rename to subiquity/filesystem/actions.py diff --git a/subiquity/models/blockdev.py b/subiquity/filesystem/blockdev.py similarity index 99% rename from subiquity/models/blockdev.py rename to subiquity/filesystem/blockdev.py index 4669c77f..3a2deaf9 100644 --- a/subiquity/models/blockdev.py +++ b/subiquity/filesystem/blockdev.py @@ -17,7 +17,7 @@ import parted import yaml from itertools import count -from subiquity.models.actions import ( +from subiquity.filesystem.actions import ( Action, PartitionAction, FormatAction, diff --git a/subiquity/views/installpath.py b/subiquity/installpath/__init__.py similarity index 74% rename from subiquity/views/installpath.py rename to subiquity/installpath/__init__.py index fd681cb4..e2a327ba 100644 --- a/subiquity/views/installpath.py +++ b/subiquity/installpath/__init__.py @@ -13,21 +13,37 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +""" Install Path + +Provides high level options for Ubuntu install + +""" import logging -from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter) +from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter, emit_signal) from subiquity.ui.lists import SimpleList from subiquity.ui.buttons import confirm_btn, cancel_btn from subiquity.ui.utils import Padding, Color - log = logging.getLogger('subiquity.installpathView') +class InstallpathModel: + """ Model representing install options + """ + + install_paths = ['Install Ubuntu', + 'Install MAAS Region Server', + 'Install MAAS Cluster Server', + 'Test installation media', + 'Test machine memory'] + selected_path = None + + class InstallpathView(WidgetWrap): - def __init__(self, model, cb): + def __init__(self, model, signal): log.debug("In install path view") self.model = model - self.cb = cb + self.signal = signal self.items = [] self.body = [ Padding.center_79(self._build_model_inputs()), @@ -54,7 +70,7 @@ class InstallpathView(WidgetWrap): height=len(sl)) def confirm(self, button): - return self.cb(button.label) + emit_signal(self.signal, 'installpath:finish', button.label) def cancel(self, button): - return self.cb(None) + emit_signal(self.signal, 'welcome:show', None) diff --git a/subiquity/models/__init__.py b/subiquity/models/__init__.py deleted file mode 100644 index eeb036aa..00000000 --- a/subiquity/models/__init__.py +++ /dev/null @@ -1,68 +0,0 @@ -# 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 . - -""" Model Classes - -Model's represent the stateful data bound from -input from the user. -""" - -import json -import yaml - - -class Model: - """Base model""" - - fields = [] - - def to_json(self): - """Marshals the model to json""" - return json.dumps(self.__dict__) - - def to_yaml(self): - """Marshals the model to yaml""" - return yaml.dump(self.__dict__) - - -class Field: - """Base field class - - New field types inherit this class, provides access to - validation checks and type definitions. - """ - default_error_messages = { - 'invalid_choice': ('Value %(value)r is not a valid choice.'), - 'blank': ('This field cannot be blank.') - } - - def __init__(self, name=None, blank=False): - self.name = name - self.blank = blank - - -class ChoiceField(Field): - """ Choices Field - - Provide a list of known options - - :param list options: list of options to choose from - """ - - def __init__(self, options): - self.options = options - - def list_options(cls): - return cls.options diff --git a/subiquity/models/filesystem.py b/subiquity/models/filesystem.py deleted file mode 100644 index 14767d2d..00000000 --- a/subiquity/models/filesystem.py +++ /dev/null @@ -1,117 +0,0 @@ -# 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 . - -""" Filesystem Model - -Provides storage device selection and additional storage -configuration. - -""" -import logging -import json -import argparse - -from subiquity import models -from subiquity.models.blockdev import Blockdev -from probert import prober -from probert.storage import StorageInfo -log = logging.getLogger('subiquity.filesystemModel') - - -class FilesystemModel(models.Model): - """ Model representing storage options - """ - - fs_menu = [ - 'Connect iSCSI network disk', - 'Connect Ceph network disk', - 'Create volume group (LVM2)', - 'Create software RAID (MD)', - 'Setup hierarchichal storage (bcache)' - ] - - partition_menu = [ - 'Add first GPT partition', - 'Format or create swap on entire device (unusual, advanced)' - ] - - supported_filesystems = [ - 'ext4', - 'xfs', - 'btrfs', - 'swap', - 'bcache cache', - 'bcache store', - 'leave unformatted' - ] - - def __init__(self): - self.storage = {} - self.info = {} - self.devices = {} - self.options = argparse.Namespace(probe_storage=True, - probe_network=False) - self.prober = prober.Prober(self.options) - self.probe_storage() - - def probe_storage(self): - self.prober.probe() - self.storage = self.prober.get_results().get('storage') - log.debug('storage probe data:\n{}'.format( - json.dumps(self.storage, indent=4, sort_keys=True))) - - # TODO: replace this with Storage.get_device_by_match() - # which takes a lambda fn for matching - VALID_MAJORS = ['8', '253'] - for disk in self.storage.keys(): - if self.storage[disk]['DEVTYPE'] == 'disk' and \ - self.storage[disk]['MAJOR'] in VALID_MAJORS: - log.debug('disk={}\n{}'.format(disk, - json.dumps(self.storage[disk], indent=4, - sort_keys=True))) - self.info[disk] = StorageInfo({disk: self.storage[disk]}) - - def get_disk(self, disk): - if disk not in self.devices: - self.devices[disk] = Blockdev(disk, self.info[disk].serial) - return self.devices[disk] - - def get_partitions(self): - partitions = [] - for dev in self.devices.values(): - partnames = [part.path for part in dev.disk.partitions] - partitions += partnames - - sorted(partitions) - return partitions - - def get_available_disks(self): - return sorted(self.info.keys()) - - def get_used_disks(self): - return [dev.disk.path for dev in self.devices.values() - if dev.available is False] - - def get_disk_info(self, disk): - return self.info[disk] - - def get_disk_action(self, disk): - return self.devices[disk].get_actions() - - def get_actions(self): - actions = [] - for dev in self.devices.values(): - actions += dev.get_actions() - return actions diff --git a/subiquity/models/identity.py b/subiquity/models/identity.py deleted file mode 100644 index d27731f3..00000000 --- a/subiquity/models/identity.py +++ /dev/null @@ -1,31 +0,0 @@ -# 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 . - -""" Identity Model - -Represents information related to identification, for example, -User's first and last name, timezone, country, language preferences. -""" - -from subiquity import models - - -class UserModel(models.Model): - """ User class to support personal information - """ - username = None - language = None - keyboard = None - timezone = None diff --git a/subiquity/models/installpath.py b/subiquity/models/installpath.py deleted file mode 100644 index 5720f7fa..00000000 --- a/subiquity/models/installpath.py +++ /dev/null @@ -1,34 +0,0 @@ -# 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 . - -""" Install Path Model - -Provides high level options for Ubuntu install - -""" - -from subiquity import models - - -class InstallpathModel(models.Model): - """ Model representing install options - """ - - install_paths = ['Install Ubuntu', - 'Install MAAS Region Server', - 'Install MAAS Cluster Server', - 'Test installation media', - 'Test machine memory'] - selected_path = None diff --git a/subiquity/models/network.py b/subiquity/models/network.py deleted file mode 100644 index e9ac9f10..00000000 --- a/subiquity/models/network.py +++ /dev/null @@ -1,103 +0,0 @@ -# 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 . - -""" Network Model - -Provides network device listings and extended network information - -""" - -import logging -from subiquity import models -import argparse -from probert import prober - -log = logging.getLogger('subiquity.networkModel') - - -class SimpleInterface: - """ A simple interface class to encapsulate network information for - particular interface - """ - def __init__(self, attrs): - self.attrs = attrs - for i in self.attrs.keys(): - if self.attrs[i] is None: - setattr(self, i, "Unknown") - else: - setattr(self, i, self.attrs[i]) - - -class NetworkModel(models.Model): - """ Model representing network interfaces - """ - - additional_options = ['Set default route', - 'Bond interfaces', - 'Install network driver'] - - def __init__(self): - self.network = {} - self.options = argparse.Namespace(probe_storage=False, - probe_network=True) - self.prober = prober.Prober(self.options) - - def probe_network(self): - self.prober.probe() - self.network = self.prober.get_results().get('network') - - def get_interfaces(self): - return [iface for iface in self.network.keys() - if self.network[iface]['type'] == 'eth' and - not self.network[iface]['hardware']['DEVPATH'].startswith( - '/devices/virtual/net')] - - def get_vendor(self, iface): - hwinfo = self.network[iface]['hardware'] - vendor_keys = [ - 'ID_VENDOR_FROM_DATABASE', - 'ID_VENDOR', - 'ID_VENDOR_ID' - ] - for key in vendor_keys: - try: - return hwinfo[key] - except KeyError: - log.warn('Failed to get key ' - '{} from interface {}'.format(key, iface)) - pass - - return 'Unknown Vendor' - - def get_model(self, iface): - hwinfo = self.network[iface]['hardware'] - model_keys = [ - 'ID_MODEL_FROM_DATABASE', - 'ID_MODEL', - 'ID_MODEL_ID' - ] - for key in model_keys: - try: - return hwinfo[key] - except KeyError: - log.warn('Failed to get key ' - '{} from interface {}'.format(key, iface)) - pass - - return 'Unknown Model' - - def get_iface_info(self, iface): - ipinfo = SimpleInterface(self.network[iface]['ip']) - return (ipinfo, self.get_vendor(iface), self.get_model(iface)) diff --git a/subiquity/models/welcome.py b/subiquity/models/welcome.py deleted file mode 100644 index 67c6460c..00000000 --- a/subiquity/models/welcome.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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 . - -""" Welcome Model - -Welcome model provides user with language selection - -""" - -from subiquity import models - - -class WelcomeModel(models.Model): - """ Model representing language selection - """ - - supported_languages = ['English', 'Belgian', 'German', 'Italian'] - selected_language = None - - def __repr__(self): - return "".format(self.selected_language) diff --git a/subiquity/views/network.py b/subiquity/network/__init__.py similarity index 55% rename from subiquity/views/network.py rename to subiquity/network/__init__.py index a7037784..ba18aee3 100644 --- a/subiquity/views/network.py +++ b/subiquity/network/__init__.py @@ -13,14 +13,98 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +""" Network Model + +Provides network device listings and extended network information + +""" + import logging +import argparse +from probert import prober from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter, Text, Columns) from subiquity.ui.lists import SimpleList from subiquity.ui.buttons import confirm_btn, cancel_btn from subiquity.ui.utils import Padding, Color -log = logging.getLogger('subiquity.networkView') +log = logging.getLogger('subiquity.network') + + +class SimpleInterface: + """ A simple interface class to encapsulate network information for + particular interface + """ + def __init__(self, attrs): + self.attrs = attrs + for i in self.attrs.keys(): + if self.attrs[i] is None: + setattr(self, i, "Unknown") + else: + setattr(self, i, self.attrs[i]) + + +class NetworkModel: + """ Model representing network interfaces + """ + + additional_options = ['Set default route', + 'Bond interfaces', + 'Install network driver'] + + def __init__(self): + self.network = {} + self.options = argparse.Namespace(probe_storage=False, + probe_network=True) + self.prober = prober.Prober(self.options) + + def probe_network(self): + self.prober.probe() + self.network = self.prober.get_results().get('network') + + def get_interfaces(self): + return [iface for iface in self.network.keys() + if self.network[iface]['type'] == 'eth' and + not self.network[iface]['hardware']['DEVPATH'].startswith( + '/devices/virtual/net')] + + def get_vendor(self, iface): + hwinfo = self.network[iface]['hardware'] + vendor_keys = [ + 'ID_VENDOR_FROM_DATABASE', + 'ID_VENDOR', + 'ID_VENDOR_ID' + ] + for key in vendor_keys: + try: + return hwinfo[key] + except KeyError: + log.warn('Failed to get key ' + '{} from interface {}'.format(key, iface)) + pass + + return 'Unknown Vendor' + + def get_model(self, iface): + hwinfo = self.network[iface]['hardware'] + model_keys = [ + 'ID_MODEL_FROM_DATABASE', + 'ID_MODEL', + 'ID_MODEL_ID' + ] + for key in model_keys: + try: + return hwinfo[key] + except KeyError: + log.warn('Failed to get key ' + '{} from interface {}'.format(key, iface)) + pass + + return 'Unknown Model' + + def get_iface_info(self, iface): + ipinfo = SimpleInterface(self.network[iface]['ip']) + return (ipinfo, self.get_vendor(iface), self.get_model(iface)) class NetworkView(WidgetWrap): diff --git a/subiquity/routes.py b/subiquity/routes.py deleted file mode 100644 index ab9f8d24..00000000 --- a/subiquity/routes.py +++ /dev/null @@ -1,81 +0,0 @@ -# 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 . - -from subiquity.controllers.welcome import WelcomeController -from subiquity.controllers.installpath import InstallpathController -from subiquity.controllers.network import NetworkController -from subiquity.controllers.filesystem import FilesystemController - - -class RoutesError(Exception): - """ Error in routes """ - pass - - -class Routes: - """ Defines application routes and maps to their controller - - Routes are inserted top down from start to finish. Maintaining - this order is required for routing to work. - """ - routes = [WelcomeController, - InstallpathController, - NetworkController, - FilesystemController] - current_route_idx = 0 - - @classmethod - def route(cls, idx): - """ Include route listing in controllers """ - try: - _route = cls.routes[idx] - except IndexError: - raise RoutesError("Failed to load Route at index: {}".format(idx)) - return _route - - @classmethod - def current_idx(cls): - """ Returns current route index """ - return cls.current_route_idx - - @classmethod - def reset(cls): - """ Resets current route """ - cls.current_route_idx = 0 - - @classmethod - def first(cls): - """ first controller/start of install """ - return cls.route(0) - - @classmethod - def last(cls): - """ end of install, last controller """ - return cls.route(-1) - - @classmethod - def current(cls): - """ return current route's controller """ - return cls.route(cls.current_route_idx) - - @classmethod - def next(cls): - cls.current_route_idx = cls.current_route_idx + 1 - return cls.route(cls.current_route_idx) - - @classmethod - def prev(cls): - cls.current_route_idx = cls.current_route_idx - 1 - return cls.route(cls.current_route_idx) diff --git a/subiquity/signals.py b/subiquity/signals.py index 1ad21ee8..f6c86006 100644 --- a/subiquity/signals.py +++ b/subiquity/signals.py @@ -15,17 +15,19 @@ import urwid -SIGNALS = {} +class Signal: + signals = [ + 'welcome:show', + 'welcome:finish', + 'installpath:show', + 'installpath:finish', + 'filesystem:show', + 'filesystem:show-disk-partition', + 'filesystem:finish-disk-partition', + 'filesystem:finish', + 'filesystem:add-disk-partition' + ] -def register_signal(obj, name): - if obj.__class__ not in SIGNALS: - SIGNALS[obj.__class__] = [] - if name not in SIGNALS[obj.__class__]: - SIGNALS[obj.__class__].append(name) - urwid.register_signal(obj.__class__, SIGNALS[obj.__class__]) - - -def emit_signal(obj, name, args): - register_signal(obj, name) - urwid.emit_signal(obj, name, args) + def register_signals(self): + urwid.register_signal(Signal, self.signals) diff --git a/subiquity/ui/frame.py b/subiquity/ui/frame.py index 6d256c98..1547706f 100644 --- a/subiquity/ui/frame.py +++ b/subiquity/ui/frame.py @@ -15,9 +15,8 @@ """ Base Frame Widget """ -from urwid import Frame, WidgetWrap +from urwid import Frame, WidgetWrap, register_signal from subiquity.ui.anchors import Header, Footer, Body -from subiquity.signals import register_signal import logging diff --git a/subiquity/views/__init__.py b/subiquity/views/__init__.py deleted file mode 100644 index 8008b43a..00000000 --- a/subiquity/views/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# 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 . diff --git a/subiquity/views/welcome.py b/subiquity/welcome/__init__.py similarity index 73% rename from subiquity/views/welcome.py rename to subiquity/welcome/__init__.py index c1b1c7eb..4e5ec2ef 100644 --- a/subiquity/views/welcome.py +++ b/subiquity/welcome/__init__.py @@ -13,16 +13,35 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter) +""" Welcome + +Welcome provides user with language selection + +""" +import logging +from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter, emit_signal) from subiquity.ui.lists import SimpleList from subiquity.ui.buttons import confirm_btn, cancel_btn from subiquity.ui.utils import Padding, Color +log = logging.getLogger('subiquity.welcome') + + +class WelcomeModel: + """ Model representing language selection + """ + + supported_languages = ['English', 'Belgian', 'German', 'Italian'] + selected_language = None + + def __repr__(self): + return "".format(self.selected_language) + class WelcomeView(WidgetWrap): - def __init__(self, model, cb): + def __init__(self, model, signal): self.model = model - self.cb = cb + self.signal = signal self.items = [] self.body = [ Padding.center_79(self._build_model_inputs()), @@ -49,7 +68,7 @@ class WelcomeView(WidgetWrap): height=len(sl)) def confirm(self, button): - return self.cb(button.label) + emit_signal(self.signal, 'welcome:finish', button.label) def cancel(self, button): - return self.cb(None) + emit_signal(self.signal, 'welcome:finish', None)