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:
parent
9c9120403f
commit
b91c961165
|
@ -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')
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -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()
|
|
@ -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']
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue