implement screens up to the point of offering the update

don't display anything about check failures yet, or actually allow the
user to start the update
This commit is contained in:
Michael Hudson-Doyle 2019-03-07 22:20:29 +13:00
parent 9c9120403f
commit b91c961165
6 changed files with 247 additions and 5 deletions

View File

@ -13,7 +13,11 @@
# 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 enum
import logging
import os
import requests.exceptions
from subiquitycore.controller import BaseController
from subiquitycore.core import Skip
@ -21,10 +25,92 @@ from subiquitycore.core import Skip
log = logging.getLogger('subiquity.controllers.refresh')
class CheckState(enum.IntEnum):
NOT_STARTED = enum.auto()
CHECKING = enum.auto()
FAILED = enum.auto()
AVAILABLE = enum.auto()
UNAVAILABLE = enum.auto()
def is_definite(self):
return self in [self.AVAILABLE, self.UNAVAILABLE]
class RefreshController(BaseController):
def default(self, index=1):
raise Skip()
signals = [
('snapd-network-change', 'snapd_network_changed'),
]
def cancel(self):
def __init__(self, common):
super().__init__(common)
self.snap_name = os.environ.get("SNAP_NAME", "subiquity")
self.check_state = CheckState.NOT_STARTED
self.view = None
self.offered_first_time = False
def snapd_network_changed(self):
# If we restarted into this version, don't check for a new version.
if self.updated:
return
# If we got an answer, don't check again.
if self.check_state.is_definite():
return
self.check_state = CheckState.CHECKING
self.run_in_bg(self._bg_check_for_update, self._check_result)
def _bg_check_for_update(self):
return self.snapd_connection.get('v2/find', select='refresh')
def _check_result(self, fut):
# If we managed to send concurrent requests and one has
# already provided an answer, just forget all about the other
# one!
if self.check_state.is_definite():
return
try:
response = fut.result()
response.raise_for_status()
except requests.exceptions.RequestException as e:
log.exception("checking for update")
self.check_state = CheckState.FAILED
return
result = response.json()
log.debug("_check_result %s", result)
for snap in result["result"]:
if snap["name"] == self.snap_name:
self.check_state = CheckState.AVAILABLE
break
else:
self.check_state = CheckState.UNAVAILABLE
if self.view:
self.view.update_check_state()
def default(self, index=1):
from subiquity.ui.views.refresh import RefreshView
if self.updated:
raise Skip()
show = False
if index == 1:
if self.check_state == CheckState.AVAILABLE:
show = True
self.offered_first_time = True
elif index == 2:
if not self.offered_first_time:
if self.check_state in [CheckState.AVAILABLE,
CheckState.CHECKING]:
show = True
else:
raise AssertionError("unexpected index {}".format(index))
if show:
self.view = RefreshView(self)
self.ui.set_body(self.view)
else:
raise Skip()
def done(self, sender=None):
self.signal.emit_signal('next-screen')
def cancel(self, sender=None):
self.signal.emit_signal('prev-screen')

View File

@ -83,6 +83,7 @@ class FakeSnapdConnection:
time.sleep(2)
def get(self, path, **args):
log.debug("snapd get %s %s", path, args)
filename = path.replace('/', '-')
if args:
filename += '-' + urlencode(sorted(args.items()))

View File

@ -0,0 +1,152 @@
# Copyright 2019 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 <http://www.gnu.org/licenses/>.
import logging
from urwid import (
Text,
)
from subiquitycore.view import BaseView
from subiquitycore.ui.buttons import done_btn, other_btn
from subiquitycore.ui.utils import button_pile, screen
from subiquity.controllers.refresh import CheckState
from subiquity.ui.spinner import Spinner
log = logging.getLogger('subiquity.ui.views.refresh')
class RefreshView(BaseView):
checking_title = _("Checking for installer update...")
checking_excerpt = _(
"Contacting the snap store to check if a new version of the "
"installer is available."
)
failed_title = _("Contacting the snap store failed")
failed_excerpt = _(
"Contacting the snap store failed:"
)
available_title = _("Installer update available")
available_excerpt = _(
"A new version of the installer is available."
)
progress_title = _("Downloading update...")
progress_excerpt = _(
"Please wait while the updated installer is being downloaded. The "
"installer will restart automatically when the download is complete."
)
def __init__(self, controller):
self.controller = controller
self.spinner = Spinner(self.controller.loop, style="dots")
if self.controller.check_state == CheckState.CHECKING:
self.check_state_checking()
elif self.controller.check_state == CheckState.AVAILABLE:
self.check_state_available()
else:
raise AssertionError(
"instantiating the view with check_state {}".format(
self.controller.check_state))
super().__init__(self._w)
def update_check_state(self):
if self.controller.check_state == CheckState.UNAVAILABLE:
self.done()
elif self.controller.check_state == CheckState.FAILED:
self.check_state_failed()
elif self.controller.check_state == CheckState.AVAILABLE:
self.check_state_available()
else:
raise AssertionError(
"update_check_state with check_state {}".format(
self.controller.check_state))
def check_state_checking(self):
self.spinner.start()
rows = [self.spinner]
buttons = [
done_btn(_("Continue without updating"), on_press=self.done),
other_btn(_("Back"), on_press=self.cancel),
]
self.title = self.checking_title
self.controller.ui.set_header(self.title)
self._w = screen(rows, buttons, excerpt=_(self.checking_excerpt))
def check_state_available(self, sender=None):
self.spinner.stop()
rows = [
Text(
_("If you choose to update, the update will be downloaded "
"and the installation will continue from here."),
)
]
buttons = button_pile([
done_btn(_("Update to the new installer"), on_press=self.update),
done_btn(_("Continue without updating"), on_press=self.done),
other_btn(_("Back"), on_press=self.cancel),
])
buttons.base_widget.focus_position = 1
self.title = self.available_title
self.controller.ui.set_header(self.available_title)
self._w = screen(rows, buttons, excerpt=_(self.available_excerpt))
def check_state_failed(self):
self.spinner.stop()
rows = [Text("<explanation goes here>")]
buttons = button_pile([
done_btn(_("Try again"), on_press=self.still_checking),
done_btn(_("Continue without updating"), on_press=self.done),
other_btn(_("Back"), on_press=self.cancel),
])
buttons.base_widget.focus_position = 1
self.title = self.failed_title
self._w = screen(rows, buttons, excerpt=_(self.failed_excerpt))
def update(self, sender=None):
self.spinner.stop()
rows = [Text("not yet")]
buttons = [
other_btn(_("Cancel update"), on_press=self.check_state_available),
]
self.controller.ui.set_header("Downloading update...")
self._w = screen(rows, buttons, excerpt=_(self.progress_excerpt))
# self.controller.start_update(self.update_started)
def done(self, result=None):
self.spinner.stop()
self.controller.done()
def cancel(self, result=None):
self.spinner.stop()
self.controller.cancel()

View File

@ -38,6 +38,8 @@ class BaseController(ABC):
self.input_filter = common['input_filter']
self.scale_factor = common['scale_factor']
self.run_in_bg = common['run_in_bg']
self.updated = common['updated']
self.application = common['application']
if 'snapd_connection' in common:
self.snapd_connection = common['snapd_connection']

View File

@ -277,6 +277,7 @@ class Application:
scale = float(os.environ.get('SUBIQUITY_REPLAY_TIMESCALE', "1"))
updated = os.path.exists(os.path.join(self.state_dir, 'updating'))
self.common = {
"application": self,
"updated": updated,
"ui": ui,
"opts": opts,

View File

@ -54,8 +54,8 @@ class Signal:
raise SignalException(
"Passed something other than a required list.")
for sig, cb in signal_callback:
if sig not in self.known_signals:
self.register_signals(sig)
# if sig not in self.known_signals:
self.register_signals(sig)
self.connect_signal(sig, cb)
def __repr__(self):