add a way to wait for something with notification after 0.1s

use this for moving between screens, removing some crummy code from subquity/core.py
This commit is contained in:
Michael Hudson-Doyle 2020-09-18 21:16:59 +12:00
parent 6c258d6da4
commit a7bcc7faf0
4 changed files with 124 additions and 70 deletions

View File

@ -29,6 +29,10 @@ from subiquitycore.tuicontroller import (
log = logging.getLogger("subiquity.controller")
class Confirm(Exception):
pass
class SubiquityController(BaseController):
autoinstall_key = None

View File

@ -46,6 +46,7 @@ from subiquity.common.errorreport import (
ErrorReporter,
ErrorReportKind,
)
from subiquity.controller import Confirm
from subiquity.journald import journald_listen
from subiquity.keycodes import (
DummyKeycodesFilter,
@ -331,38 +332,6 @@ class Subiquity(TuiApplication):
self.show_progress_handle.cancel()
self.show_progress_handle = None
def next_screen(self):
can_install = all(e.is_set() for e in self.base_model.install_events)
if can_install and not self.install_confirmed:
if self.interactive():
log.debug("showing InstallConfirmation over %s", self.ui.body)
from subiquity.ui.views.installprogress import (
InstallConfirmation,
)
self._cancel_show_progress()
self.add_global_overlay(
InstallConfirmation(self.ui.body, self))
else:
yes = _('yes')
no = _('no')
answer = no
if 'autoinstall' in self.kernel_cmdline:
answer = yes
else:
print(_("Confirmation is required to continue."))
print(_("Add 'autoinstall' to your kernel command line to"
" avoid this"))
print()
prompt = "\n\n{} ({}|{})".format(
_("Continue with autoinstall?"), yes, no)
while answer != yes:
print(prompt)
answer = input()
self.confirm_install()
super().next_screen()
else:
super().next_screen()
def interactive(self):
if not self.autoinstall_config:
return True
@ -386,42 +355,56 @@ class Subiquity(TuiApplication):
break
super().select_initial_screen(index)
async def select_screen(self, new):
async def move_screen(self, increment, coro):
try:
await super().move_screen(increment, coro)
except Confirm:
if self.interactive():
log.debug("showing InstallConfirmation over %s", self.ui.body)
from subiquity.ui.views.installprogress import (
InstallConfirmation,
)
self.add_global_overlay(
InstallConfirmation(self.ui.body, self))
else:
yes = _('yes')
no = _('no')
answer = no
if 'autoinstall' in self.kernel_cmdline:
answer = yes
else:
print(_("Confirmation is required to continue."))
print(_("Add 'autoinstall' to your kernel command line to"
" avoid this"))
print()
prompt = "\n\n{} ({}|{})".format(
_("Continue with autoinstall?"), yes, no)
while answer != yes:
print(prompt)
answer = input()
self.confirm_install()
self.next_screen()
async def make_view_for_controller(self, new):
can_install = all(e.is_set() for e in self.base_model.install_events)
if can_install and not self.install_confirmed:
if new.model_name:
if not self.base_model.is_configured(new.model_name):
raise Confirm
if new.interactive():
self._cancel_show_progress()
if self.progress_showing:
shown_for = self.aio_loop.time() - self.progress_shown_time
remaining = 1.0 - shown_for
if remaining > 0.0:
self.aio_loop.call_later(
remaining, self.select_screen, new)
return
self.progress_showing = False
await super().select_screen(new)
if new.answers:
new.run_answers()
elif self.autoinstall_config and not new.autoinstall_applied:
if self.interactive() and self.show_progress_handle is None:
self.ui.block_input = True
self.show_progress_handle = self.aio_loop.call_later(
0.1, self._show_progress)
schedule_task(self._apply(new))
self.aio_loop.call_soon(new.run_answers)
return await super().make_view_for_controller(new)
else:
if self.autoinstall_config and not new.autoinstall_applied:
await new.apply_autoinstall_config()
new.autoinstall_applied = True
new.configured()
raise Skip
def _show_progress(self):
self.ui.block_input = False
self.progress_shown_time = self.aio_loop.time()
self.progress_showing = True
def show_progress(self):
self.ui.set_body(self.controllers.InstallProgress.progress_view)
async def _apply(self, controller):
await controller.apply_autoinstall_config()
controller.autoinstall_applied = True
controller.configured()
self.next_screen()
def _network_change(self):
self.signal.emit_signal('snapd-network-change')

View File

@ -130,8 +130,21 @@ class SubiquityModel:
}
def configured(self, model_name):
log.debug("model %s is configured", model_name)
self._events[model_name].set()
if model_name in INSTALL_MODEL_NAMES:
unconfigured = {
mn for mn in INSTALL_MODEL_NAMES
if not self.is_configured(mn)
}
elif model_name in POSTINSTALL_MODEL_NAMES:
unconfigured = {
mn for mn in POSTINSTALL_MODEL_NAMES
if not self.is_configured(mn)
}
log.debug("model %s is configured, to go %s", model_name, unconfigured)
def is_configured(self, model_name):
return self._events[model_name].is_set()
def get_target_groups(self):
command = ['chroot', self.target, 'getent', 'group']

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/>.
import asyncio
import inspect
import logging
import os
@ -49,6 +50,14 @@ def extend_dec_special_charmap():
})
# When waiting for something of unknown duration, block the UI for at
# most this long before showing an indication of progress.
MAX_BLOCK_TIME = 0.1
# If an indication of progress is shown, show it for at least this
# long to avoid excessive flicker in the UI.
MIN_SHOW_PROGRESS_TIME = 1.0
class TuiApplication(Application):
make_ui = SubiquityCoreUI
@ -97,7 +106,7 @@ class TuiApplication(Application):
before_hook()
schedule_task(_run())
async def select_screen(self, new):
async def make_view_for_controller(self, new):
new.context.enter("starting UI")
if self.opts.screens and new.name not in self.opts.screens:
raise Skip
@ -111,10 +120,46 @@ class TuiApplication(Application):
new.context.exit("(skipped)")
raise
else:
self.ui.set_body(view)
self.cur_screen = new
with open(self.state_path('last-screen'), 'w') as fp:
fp.write(new.name)
with open(self.state_path('last-screen'), 'w') as fp:
fp.write(new.name)
return view
async def _wait_with_indication(self, awaitable, show, hide=None):
"""Wait for something but tell the user if it takes a while.
When waiting for something that can take an unknown length of
time, we want to tell the user if it takes more than a moment
(defined as MAX_BLOCK_TIME) but make sure that we display any
indication for long enough that the UI is not flickering
incomprehensively (MIN_SHOW_PROGRESS_TIME).
"""
min_show_task = None
def _show():
self.ui.block_input = False
nonlocal min_show_task
min_show_task = self.aio_loop.create_task(
asyncio.sleep(MIN_SHOW_PROGRESS_TIME))
show()
self.ui.block_input = True
show_handle = self.aio_loop.call_later(MAX_BLOCK_TIME, _show)
try:
result = await awaitable
finally:
if min_show_task:
await min_show_task
if hide is not None:
hide()
else:
self.ui.block_input = False
show_handle.cancel()
return result
def show_progress(self):
raise NotImplementedError
async def _move_screen(self, increment, coro):
if coro is not None:
@ -129,24 +174,33 @@ class TuiApplication(Application):
self.controllers.index += increment
if self.controllers.index < 0:
self.controllers.index = cur_index
return
return None
if self.controllers.index >= len(self.controllers.instances):
self.exit()
return
return None
new = self.controllers.cur
try:
await self.select_screen(new)
return await self.make_view_for_controller(new)
except Skip:
log.debug("skipping screen %s", new.name)
continue
except Exception:
self.controllers.index = cur_index
raise
else:
return
async def move_screen(self, increment, coro):
view = await self._wait_with_indication(
self._move_screen(increment, coro), self.show_progress)
if view is not None:
self.ui.set_body(view)
def next_screen(self, coro=None):
self.aio_loop.create_task(self._move_screen(1, coro))
self.aio_loop.create_task(self.move_screen(1, coro))
def prev_screen(self):
self.aio_loop.create_task(self._move_screen(-1, None))
self.aio_loop.create_task(self.move_screen(-1, None))
def select_initial_screen(self, controller_index):
for controller in self.controllers.instances[:controller_index]: