refactor how controllers are stored a bit

This commit is contained in:
Michael Hudson-Doyle 2019-11-18 13:55:43 +13:00
parent 9e0c0bf106
commit 64378cdb95
8 changed files with 110 additions and 101 deletions

View File

@ -236,7 +236,7 @@ class IdentityController(BaseController):
login_details_path = '/run/console-conf/login-details.txt'
self.model.add_user(result)
ips = []
net_model = self.app.controller_instances['Network'].model
net_model = self.app.base_model.network
for dev in net_model.get_all_netdevs():
ips.extend(dev.actual_global_ip_addresses)
with open(login_details_path, 'w') as fp:
@ -252,7 +252,7 @@ class IdentityController(BaseController):
title = "Configuration Complete"
self.ui.set_header(title)
net_model = self.app.controller_instances['Network'].model
net_model = self.app.base_model.network
ifaces = net_model.get_all_netdevs()
login_view = LoginView(self.opts, self.model, self, ifaces)

View File

@ -13,6 +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/>.
from .error import ErrorController
from .filesystem import FilesystemController
from .identity import IdentityController
from .installprogress import InstallProgressController
@ -26,6 +27,7 @@ from .ssh import SSHController
from .welcome import WelcomeController
from .zdev import ZdevController
__all__ = [
'ErrorController',
'FilesystemController',
'IdentityController',
'InstallProgressController',

View File

@ -32,6 +32,7 @@ import requests
import urwid
from subiquitycore.controller import BaseController
from subiquitycore.core import Skip
from subiquity.async_helpers import (
run_in_thread,
@ -357,7 +358,7 @@ class ErrorController(BaseController):
return r
def start_ui(self):
pass
raise Skip
def cancel(self):
pass

View File

@ -24,7 +24,6 @@ import apport.hookutils
from subiquitycore.core import Application
from subiquity.controllers.error import (
ErrorController,
ErrorReportKind,
)
from subiquity.models.subiquity import SubiquityModel
@ -83,6 +82,7 @@ class Subiquity(Application):
"SSH",
"SnapList",
"InstallProgress",
"Error", # does not have a UI
]
def __init__(self, opts, block_log_dir):
@ -119,7 +119,7 @@ class Subiquity(Application):
def select_initial_screen(self, index):
super().select_initial_screen(index)
for report in self.error_controller.reports:
for report in self.controllers.Error.reports:
if report.kind == ErrorReportKind.UI and not report.seen:
log.debug("showing new error %r", report.base)
self.show_error_report(report)
@ -163,14 +163,6 @@ class Subiquity(Application):
self.run_command_in_foreground(
"bash", before_hook=_before, cwd='/')
def load_controllers(self):
super().load_controllers()
self.error_controller = ErrorController(self)
def start_controllers(self):
super().start_controllers()
self.error_controller.start()
def note_file_for_apport(self, key, path):
self._apport_files.append((key, path))
@ -181,7 +173,7 @@ class Subiquity(Application):
log.debug("generating crash report")
try:
report = self.error_controller.create_report(kind)
report = self.controllers.Error.create_report(kind)
except Exception:
log.exception("creating crash report failed")
return

View File

@ -280,7 +280,7 @@ class ErrorReportListStretchy(Stretchy):
Text(""),
])]
self.report_to_row = {}
for report in self.app.error_controller.reports:
for report in self.app.controllers.Error.reports:
connect_signal(report, "changed", self._report_changed, report)
r = self.report_to_row[report] = self.row_for_report(report)
rows.append(r)

View File

@ -195,7 +195,7 @@ class HelpMenu(WidgetWrap):
local = Text(
('info_minor header', " " + _("Help on this screen") + " "))
if self.parent.app.error_controller.reports:
if self.parent.app.controllers.Error.reports:
view_errors = menu_item(
_("View error reports").format(local_title),
on_press=self._show_errors)

View File

@ -26,11 +26,8 @@ class BaseController(ABC):
signals = []
@classmethod
def _controller_name(cls):
return cls.__name__[:-len("Controller")]
def __init__(self, app):
self.name = type(self).__name__[:-len("Controller")]
self.ui = app.ui
self.signal = app.signal
self.opts = app.opts
@ -47,7 +44,7 @@ class BaseController(ABC):
self.loop = app.loop
self.run_in_bg = app.run_in_bg
self.app = app
self.answers = app.answers.get(self._controller_name(), {})
self.answers = app.answers.get(self.name, {})
def register_signals(self):
"""Defines signals associated with controller from model."""
@ -72,10 +69,7 @@ class BaseController(ABC):
@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
return self.app.controllers.cur is self
@abstractmethod
def start_ui(self):
@ -144,6 +138,7 @@ class BaseController(ABC):
class RepeatedController(BaseController):
def __init__(self, orig, index):
self.name = "{}-{}".format(orig.name, index)
self.orig = orig
self.index = index

View File

@ -282,6 +282,59 @@ class AsyncioEventLoop(urwid.AsyncioEventLoop):
loop.default_exception_handler(context)
class ControllerSet:
def __init__(self, app, names):
self.app = app
self.controller_names = names
self.index = -1
self.instances = []
def load(self):
controllers_mod = __import__(
'{}.controllers'.format(self.app.project), None, None, [''])
for name in self.controller_names:
log.debug("Importing controller: %s", name)
klass = getattr(controllers_mod, name+"Controller")
if hasattr(self, name):
c = 0
for instance in self.instances:
if isinstance(instance, klass):
c += 1
inst = RepeatedController(getattr(self, name), c)
name = inst.name
else:
inst = klass(self.app)
setattr(self, name, inst)
self.instances.append(inst)
@property
def cur(self):
if self.index >= 0:
inst = self.instances[self.index]
while isinstance(inst, RepeatedController):
inst = inst.orig
return inst
else:
return None
@property
def at_end(self):
return self.index == len(self.instances) - 1
@property
def at_start(self):
return self.index == 0
def advance(self):
self.index += 1
return self.cur
def back_up(self):
self.index -= 1
return self.cur
class Application:
# A concrete subclass must set project and controllers attributes, e.g.:
@ -295,7 +348,7 @@ class Application:
# "InstallProgress",
# ]
# The 'next-screen' and 'prev-screen' signals move through the list of
# controllers in order, calling the default method on the controller
# controllers in order, calling the start_ui method on the controller
# instance.
make_ui = SubiquityCoreUI
@ -337,21 +390,7 @@ class Application:
self.prober = prober
self.loop = None
self.pool = futures.ThreadPoolExecutor(10)
if opts.screens:
self.controllers = [c for c in self.controllers
if c in opts.screens]
else:
self.controllers = self.controllers[:]
self.ui.progress_completion = len(self.controllers)
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]
self.controllers = ControllerSet(self, self.controllers)
def run_in_bg(self, func, callback):
"""Run func() in a thread and call callback on UI thread.
@ -423,58 +462,58 @@ class Application:
self.signal.connect_signals(signals)
# Registers signals from each controller
for controller_class in self.controller_instances.values():
controller_class.register_signals()
for controller in self.controllers.instances:
controller.register_signals()
log.debug("known signals: %s", self.signal.known_signals)
def save_state(self):
cur_controller = self.cur_controller
if cur_controller is None:
cur = self.controllers.cur
if cur is None:
return
state_path = os.path.join(
self.state_dir, 'states', cur_controller._controller_name())
self.state_dir, 'states', cur.name)
with open(state_path, 'w') as fp:
json.dump(cur_controller.serialize(), fp)
json.dump(cur.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
log.debug(
"moving to screen %s", self.cur_controller._controller_name())
self.cur_controller.start_ui()
def start_screen(self, new):
old = self.controllers.cur
if old is not None:
old.end_ui()
log.debug("moving to screen %s", new.name)
if self.opts.screens and new.name not in self.opts.screens:
raise Skip
new.start_ui()
state_path = os.path.join(self.state_dir, 'last-screen')
with open(state_path, 'w') as fp:
fp.write(self.cur_controller._controller_name())
fp.write(new.name)
def next_screen(self, *args):
self.save_state()
while True:
if self.controller_index == len(self.controllers) - 1:
if self.controllers.at_end:
self.exit()
controller = self.controllers.advance()
try:
self.select_screen(self.controller_index + 1)
self.start_screen(controller)
except Skip:
controller_name = self.controllers[self.controller_index]
log.debug("skipping screen %s", controller_name)
log.debug("skipping screen %s", controller.name)
continue
else:
return
break
def prev_screen(self, *args):
self.save_state()
while True:
if self.controller_index == 0:
if self.controllers.at_start:
self.exit()
controller = self.controllers.back_up()
try:
self.select_screen(self.controller_index - 1)
self.start_screen(controller)
except Skip:
controller_name = self.controllers[self.controller_index]
log.debug("skipping screen %s", controller_name)
log.debug("skipping screen %s", controller.name)
continue
else:
return
break
# EventLoop -------------------------------------------------------------------
@ -566,56 +605,36 @@ class Application:
elif key in ['ctrl t', 'f4']:
self.toggle_color()
def load_controllers(self):
log.debug("load_controllers")
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("load_controllers done")
def start_controllers(self):
log.debug("starting controllers")
for k in self.controllers:
self.controller_instances[k].start()
for controller in self.controllers.instances:
controller.start()
log.debug("controllers started")
def load_serialized_state(self):
for k in self.controllers:
state_path = os.path.join(self.state_dir, 'states', k)
for controller in self.controllers.instances:
state_path = os.path.join(
self.state_dir, 'states', controller.name)
if not os.path.exists(state_path):
continue
with open(state_path) as fp:
self.controller_instances[k].deserialize(
json.load(fp))
controller.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()
controller_index = 0
for i, controller in enumerate(self.controllers.instances):
if controller.name == last_screen:
controller_index = i
return controller_index
if last_screen in self.controllers:
return self.controllers.index(last_screen)
else:
return 0
def select_initial_screen(self, index):
def select_initial_screen(self, controller_index):
self.controllers.index = controller_index
try:
self.select_screen(index)
self.start_screen(self.controllers.cur)
except Skip:
self.next_screen()
@ -642,7 +661,7 @@ class Application:
if self.opts.scripts:
self.run_scripts(self.opts.scripts)
self.load_controllers()
self.controllers.load()
initial_controller_index = 0