From 3605514d301a6134da71a89c79a2019103255b3d Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Tue, 3 Sep 2019 12:58:57 +1200 Subject: [PATCH 1/3] extract some methods out of Application.run --- subiquitycore/core.py | 78 ++++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/subiquitycore/core.py b/subiquitycore/core.py index f49f4361..e1a0588a 100644 --- a/subiquitycore/core.py +++ b/subiquitycore/core.py @@ -466,6 +466,46 @@ class Application: if key == 'ctrl x': self.signal.emit_signal('control-x-quit') + def load_controllers(self): + controllers_mod = __import__( + '{}.controllers'.format(self.project), None, None, ['']) + for i, k in enumerate(self.controllers): + if self.controller_instances[k] is None: + log.debug("Importing controller: {}".format(k)) + klass = getattr(controllers_mod, k+"Controller") + self.controller_instances[k] = klass(self) + else: + count = 1 + for k2 in self.controllers[:i]: + if k2 == k or k2.startswith(k + '-'): + count += 1 + orig = self.controller_instances[k] + k += '-' + str(count) + self.controllers[i] = k + self.controller_instances[k] = RepeatedController( + orig, count) + log.debug("*** %s", self.controller_instances) + + def load_serialized_state(self): + for k in self.controllers: + state_path = os.path.join(self.state_dir, 'states', k) + if not os.path.exists(state_path): + continue + with open(state_path) as fp: + self.controller_instances[k].deserialize( + json.load(fp)) + + last_screen = None + state_path = os.path.join(self.state_dir, 'last-screen') + if os.path.exists(state_path): + with open(state_path) as fp: + last_screen = fp.read().strip() + + if last_screen in self.controllers: + return self.controllers.index(last_screen) + else: + return 0 + def run(self): if (self.opts.run_on_serial and os.ttyname(0) != "/dev/ttysclp0"): @@ -486,45 +526,13 @@ class Application: try: if self.opts.scripts: self.run_scripts(self.opts.scripts) - controllers_mod = __import__('%s.controllers' % self.project, - None, None, ['']) - for i, k in enumerate(self.controllers): - if self.controller_instances[k] is None: - log.debug("Importing controller: {}".format(k)) - klass = getattr(controllers_mod, k+"Controller") - self.controller_instances[k] = klass(self) - else: - count = 1 - for k2 in self.controllers[:i]: - if k2 == k or k2.startswith(k + '-'): - count += 1 - orig = self.controller_instances[k] - k += '-' + str(count) - self.controllers[i] = k - self.controller_instances[k] = RepeatedController( - orig, count) - log.debug("*** %s", self.controller_instances) + + self.load_controllers() initial_controller_index = 0 if self.updated: - for k in self.controllers: - state_path = os.path.join(self.state_dir, 'states', k) - if not os.path.exists(state_path): - continue - with open(state_path) as fp: - self.controller_instances[k].deserialize( - json.load(fp)) - - last_screen = None - state_path = os.path.join(self.state_dir, 'last-screen') - if os.path.exists(state_path): - with open(state_path) as fp: - last_screen = fp.read().strip() - - if last_screen in self.controllers: - initial_controller_index = self.controllers.index( - last_screen) + initial_controller_index = self.load_serialized_state() def select_initial_screen(loop, index): try: From 9a056531505574f4a47773f9c05cd8762fa24787 Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Tue, 3 Sep 2019 13:04:47 +1200 Subject: [PATCH 2/3] Tweak Controller API * rename 'default' method to 'start_ui' * add 'end_ui' hook * add Application.cur_controller helper --- console_conf/controllers/identity.py | 2 +- console_conf/controllers/welcome.py | 2 +- subiquity/controllers/filesystem.py | 2 +- subiquity/controllers/identity.py | 2 +- subiquity/controllers/installprogress.py | 2 +- subiquity/controllers/keyboard.py | 2 +- subiquity/controllers/mirror.py | 2 +- subiquity/controllers/proxy.py | 2 +- subiquity/controllers/refresh.py | 2 +- subiquity/controllers/snaplist.py | 2 +- subiquity/controllers/ssh.py | 2 +- subiquity/controllers/welcome.py | 2 +- subiquity/controllers/zdev.py | 2 +- subiquitycore/controller.py | 27 ++++++++++++++++++++---- subiquitycore/controllers/network.py | 7 +++--- subiquitycore/core.py | 25 ++++++++++++++-------- 16 files changed, 56 insertions(+), 29 deletions(-) diff --git a/console_conf/controllers/identity.py b/console_conf/controllers/identity.py index 77af9555..83c4d1c2 100644 --- a/console_conf/controllers/identity.py +++ b/console_conf/controllers/identity.py @@ -198,7 +198,7 @@ class IdentityController(BaseController): super().__init__(app) self.model = app.base_model.identity - def default(self): + def start_ui(self): footer = "" self.ui.set_footer(footer) self.ui.set_body(IdentityView(self.model, self)) diff --git a/console_conf/controllers/welcome.py b/console_conf/controllers/welcome.py index 3d7a7f85..10c932a8 100644 --- a/console_conf/controllers/welcome.py +++ b/console_conf/controllers/welcome.py @@ -20,7 +20,7 @@ from subiquitycore.controller import BaseController class WelcomeController(BaseController): - def default(self): + def start_ui(self): view = WelcomeView(self) self.ui.set_body(view) diff --git a/subiquity/controllers/filesystem.py b/subiquity/controllers/filesystem.py index aa73c065..d7e1d67d 100644 --- a/subiquity/controllers/filesystem.py +++ b/subiquity/controllers/filesystem.py @@ -125,7 +125,7 @@ class FilesystemController(BaseController): lambda fut: self._probed(fut, True), ) - def default(self): + def start_ui(self): self.showing = True if self._probe_state in [ProbeState.PROBING, ProbeState.REPROBING]: diff --git a/subiquity/controllers/identity.py b/subiquity/controllers/identity.py index 2e48ecea..2a4f8c1e 100644 --- a/subiquity/controllers/identity.py +++ b/subiquity/controllers/identity.py @@ -28,7 +28,7 @@ class IdentityController(BaseController): super().__init__(app) self.model = app.base_model.identity - def default(self): + def start_ui(self): self.ui.set_body(IdentityView(self.model, self)) if all(elem in self.answers for elem in ['realname', 'username', 'password', 'hostname']): diff --git a/subiquity/controllers/installprogress.py b/subiquity/controllers/installprogress.py index 21a5c921..c2ea3399 100644 --- a/subiquity/controllers/installprogress.py +++ b/subiquity/controllers/installprogress.py @@ -564,7 +564,7 @@ class InstallProgressController(BaseController): utils.disable_subiquity() self.signal.emit_signal('quit') - def default(self): + def start_ui(self): self.progress_view_showing = True if self.install_state == InstallState.RUNNING: self.progress_view.title = _("Installing system") diff --git a/subiquity/controllers/keyboard.py b/subiquity/controllers/keyboard.py index f798feb3..a7324794 100644 --- a/subiquity/controllers/keyboard.py +++ b/subiquity/controllers/keyboard.py @@ -42,7 +42,7 @@ class KeyboardController(BaseController): log.debug("loading launguage %s", code) self.model.load_language(code) - def default(self): + def start_ui(self): if self.model.current_lang is None: self.model.load_language('C') view = KeyboardView(self.model, self, self.opts) diff --git a/subiquity/controllers/mirror.py b/subiquity/controllers/mirror.py index 520a8308..c694f002 100644 --- a/subiquity/controllers/mirror.py +++ b/subiquity/controllers/mirror.py @@ -82,7 +82,7 @@ class MirrorController(BaseController): self.check_state = CheckState.DONE self.model.set_country(cc) - def default(self): + def start_ui(self): self.check_state = CheckState.DONE self.ui.set_body(MirrorView(self.model, self)) if 'mirror' in self.answers: diff --git a/subiquity/controllers/proxy.py b/subiquity/controllers/proxy.py index b285626b..d7c74d8e 100644 --- a/subiquity/controllers/proxy.py +++ b/subiquity/controllers/proxy.py @@ -29,7 +29,7 @@ class ProxyController(BaseController): super().__init__(app) self.model = app.base_model.proxy - def default(self): + def start_ui(self): self.ui.set_body(ProxyView(self.model, self)) if 'proxy' in self.answers: self.done(self.answers['proxy']) diff --git a/subiquity/controllers/refresh.py b/subiquity/controllers/refresh.py index 9be415f1..2850939b 100644 --- a/subiquity/controllers/refresh.py +++ b/subiquity/controllers/refresh.py @@ -248,7 +248,7 @@ class RefreshController(BaseController): result = response.json() callback(result['result']) - def default(self, index=1): + def start_ui(self, index=1): from subiquity.ui.views.refresh import RefreshView if self.app.updated: raise Skip() diff --git a/subiquity/controllers/snaplist.py b/subiquity/controllers/snaplist.py index 9eb7dcb6..41195a9b 100644 --- a/subiquity/controllers/snaplist.py +++ b/subiquity/controllers/snaplist.py @@ -154,7 +154,7 @@ class SnapListController(BaseController): self.loader = self._make_loader() self.loader.start() - def default(self): + def start_ui(self): if self.loader.failed or not self.app.base_model.network.has_network: # If loading snaps failed or the network is disabled, skip the # screen. diff --git a/subiquity/controllers/ssh.py b/subiquity/controllers/ssh.py index b75e33bb..8ce98832 100644 --- a/subiquity/controllers/ssh.py +++ b/subiquity/controllers/ssh.py @@ -35,7 +35,7 @@ class SSHController(BaseController): super().__init__(app) self.model = app.base_model.ssh - def default(self): + def start_ui(self): self.ui.set_body(SSHView(self.model, self)) if self.answers: d = { diff --git a/subiquity/controllers/welcome.py b/subiquity/controllers/welcome.py index d0a86805..5cbc73fa 100644 --- a/subiquity/controllers/welcome.py +++ b/subiquity/controllers/welcome.py @@ -39,7 +39,7 @@ class WelcomeController(BaseController): if code == lang: self.model.switch_language(code) - def default(self): + def start_ui(self): view = WelcomeView(self.model, self) self.ui.set_body(view) if 'lang' in self.answers: diff --git a/subiquity/controllers/zdev.py b/subiquity/controllers/zdev.py index d229ff96..dcb406c1 100644 --- a/subiquity/controllers/zdev.py +++ b/subiquity/controllers/zdev.py @@ -640,7 +640,7 @@ class ZdevController(BaseController): zdevinfos = [ZdevInfo.from_row(row) for row in devices] self.zdevinfos = OrderedDict([(i.id, i) for i in zdevinfos]) - def default(self): + def start_ui(self): if 'accept-default' in self.answers: self.done() self.ui.set_body(ZdevView(self)) diff --git a/subiquitycore/controller.py b/subiquitycore/controller.py index a937ddf2..5f347d57 100644 --- a/subiquitycore/controller.py +++ b/subiquitycore/controller.py @@ -46,6 +46,13 @@ class BaseController(ABC): self.signal.connect_signals(signals) def start(self): + """Called just before the main loop is started. + + At the time this is called, all controllers and models and so on + have been created. This is when the controller should start + interacting with the outside world, e.g. probing for network + devices or start making connections to the snap store. + """ pass @abstractmethod @@ -53,8 +60,20 @@ class BaseController(ABC): pass @abstractmethod - def default(self): - pass + def start_ui(self): + """Start running this controller's UI. + + This method should call self.ui.set_body. + """ + + def end_ui(self): + """Stop running this controller's UI. + + This method doesn't actually need to remove this controller's UI + as the next one is about to replace it, it's more of a hook to + stop any background tasks that can be stopped when the UI is not + running. + """ def serialize(self): return None @@ -113,8 +132,8 @@ class RepeatedController(BaseController): def register_signals(self): pass - def default(self): - self.orig.default(self.index) + def start_ui(self): + self.orig.start_ui(self.index) def cancel(self): self.orig.cancel() diff --git a/subiquitycore/controllers/network.py b/subiquitycore/controllers/network.py index 56a3e684..d2200da9 100644 --- a/subiquitycore/controllers/network.py +++ b/subiquitycore/controllers/network.py @@ -244,13 +244,11 @@ class NetworkController(BaseController): def done(self): log.debug("NetworkController.done next-screen") - self.view = None self.model.has_network = bool( self.network_event_receiver.default_routes) self.signal.emit_signal('next-screen') def cancel(self): - self.view = None self.signal.emit_signal('prev-screen') def _action_get(self, id): @@ -339,7 +337,7 @@ class NetworkController(BaseController): dev.set_dhcp_state(v, "TIMEDOUT") self.network_event_receiver.update_link(dev.ifindex) - def default(self): + def start_ui(self): if not self.view_shown: self.update_initial_configs() self.view = NetworkView(self.model, self) @@ -349,6 +347,9 @@ class NetworkController(BaseController): self.network_event_receiver.view = self.view self.ui.set_body(self.view) + def end_ui(self): + self.view = self.network_event_receiver.view = None + @property def netplan_path(self): if self.opts.project == "subiquity": diff --git a/subiquitycore/core.py b/subiquitycore/core.py index e1a0588a..82861fd7 100644 --- a/subiquitycore/core.py +++ b/subiquitycore/core.py @@ -278,6 +278,13 @@ class Application: self.controller_instances = dict.fromkeys(self.controllers) self.controller_index = -1 + @property + def cur_controller(self): + if self.controller_index < 0: + return None + controller_name = self.controllers[self.controller_index] + return self.controller_instances[controller_name] + def run_in_bg(self, func, callback): """Run func() in a thread and call callback on UI thread. @@ -339,25 +346,25 @@ class Application: log.debug(self.signal) def save_state(self): - if self.controller_index < 0: + cur_controller = self.cur_controller + if cur_controller is None: return - cur_controller_name = self.controllers[self.controller_index] - cur_controller = self.controller_instances[cur_controller_name] state_path = os.path.join( - self.state_dir, 'states', cur_controller_name) + self.state_dir, 'states', cur_controller._controller_name()) with open(state_path, 'w') as fp: json.dump(cur_controller.serialize(), fp) def select_screen(self, index): + if self.cur_controller is not None: + self.cur_controller.end_ui() self.controller_index = index self.ui.progress_current = index - controller_name = self.controllers[self.controller_index] - log.debug("moving to screen %s", controller_name) - controller = self.controller_instances[controller_name] - controller.default() + log.debug( + "moving to screen %s", self.cur_controller._controller_name()) + self.cur_controller.start_ui() state_path = os.path.join(self.state_dir, 'last-screen') with open(state_path, 'w') as fp: - fp.write(controller_name) + fp.write(self.cur_controller._controller_name()) def next_screen(self, *args): self.save_state() From c1e973fe1ee74f58b9adc71c1e6681c109cf1c51 Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Tue, 3 Sep 2019 13:10:43 +1200 Subject: [PATCH 3/3] Add Controller.showing helper --- subiquity/controllers/filesystem.py | 4 ---- subiquity/controllers/installprogress.py | 4 +--- subiquity/controllers/refresh.py | 12 ++++-------- subiquitycore/controller.py | 7 +++++++ 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/subiquity/controllers/filesystem.py b/subiquity/controllers/filesystem.py index d7e1d67d..e34971b0 100644 --- a/subiquity/controllers/filesystem.py +++ b/subiquity/controllers/filesystem.py @@ -65,7 +65,6 @@ class FilesystemController(BaseController): self.answers.setdefault('guided', False) self.answers.setdefault('guided-index', 0) self.answers.setdefault('manual', []) - self.showing = False self._probe_state = ProbeState.NOT_STARTED def start(self): @@ -126,7 +125,6 @@ class FilesystemController(BaseController): ) def start_ui(self): - self.showing = True if self._probe_state in [ProbeState.PROBING, ProbeState.REPROBING]: self.ui.set_body(SlowProbing(self)) @@ -254,11 +252,9 @@ class FilesystemController(BaseController): self.manual() def cancel(self): - self.showing = False self.signal.emit_signal('prev-screen') def finish(self): - self.showing = False log.debug("FilesystemController.finish next-screen") # start curtin install in background self.signal.emit_signal('installprogress:filesystem-config-done') diff --git a/subiquity/controllers/installprogress.py b/subiquity/controllers/installprogress.py index c2ea3399..0fe82d8a 100644 --- a/subiquity/controllers/installprogress.py +++ b/subiquity/controllers/installprogress.py @@ -211,7 +211,6 @@ class InstallProgressController(BaseController): self.model = app.base_model self.answers.setdefault('reboot', False) self.progress_view = None - self.progress_view_showing = False self.install_state = InstallState.NOT_STARTED self.journal_listener_handle = None self._postinstall_prerequisites = { @@ -373,7 +372,7 @@ class InstallProgressController(BaseController): self.install_state = InstallState.DONE log.debug('After curtin install OK') self.ui.progress_current += 1 - if not self.progress_view_showing: + if not self.showing: self.ui.set_footer(_("Install complete")) else: # Re-set footer so progress bar updates. @@ -565,7 +564,6 @@ class InstallProgressController(BaseController): self.signal.emit_signal('quit') def start_ui(self): - self.progress_view_showing = True if self.install_state == InstallState.RUNNING: self.progress_view.title = _("Installing system") footer = _("Thank you for using Ubuntu!") diff --git a/subiquity/controllers/refresh.py b/subiquity/controllers/refresh.py index 2850939b..0fd54f29 100644 --- a/subiquity/controllers/refresh.py +++ b/subiquity/controllers/refresh.py @@ -60,7 +60,6 @@ class RefreshController(BaseController): self.current_snap_version = "unknown" self.new_snap_version = "" - self.view = None self.offered_first_time = False def start(self): @@ -201,8 +200,8 @@ class RefreshController(BaseController): break else: self.check_state = CheckState.UNAVAILABLE - if self.view: - self.view.update_check_state() + if self.showing: + self.ui.body.update_check_state() def start_update(self, callback): update_marker = os.path.join(self.app.state_dir, 'updating') @@ -265,11 +264,10 @@ class RefreshController(BaseController): else: raise AssertionError("unexpected index {}".format(index)) if show: - self.view = RefreshView(self) - self.ui.set_body(self.view) + self.ui.set_body(RefreshView(self)) if 'update' in self.answers: if self.answers['update']: - self.view.update() + self.ui.body.update() else: self.done() else: @@ -277,9 +275,7 @@ class RefreshController(BaseController): def done(self, sender=None): log.debug("RefreshController.done next-screen") - self.view = None self.signal.emit_signal('next-screen') def cancel(self, sender=None): - self.view = None self.signal.emit_signal('prev-screen') diff --git a/subiquitycore/controller.py b/subiquitycore/controller.py index 5f347d57..f7ec91b5 100644 --- a/subiquitycore/controller.py +++ b/subiquitycore/controller.py @@ -59,6 +59,13 @@ class BaseController(ABC): def cancel(self): pass + @property + def showing(self): + cur_controller = self.app.cur_controller + while isinstance(cur_controller, RepeatedController): + cur_controller = cur_controller.orig + return cur_controller is self + @abstractmethod def start_ui(self): """Start running this controller's UI.