straigthen out refresh logic and handle failed update better
This commit is contained in:
parent
930d479e85
commit
c44a15d4c3
|
@ -33,16 +33,10 @@ log = logging.getLogger('subiquity.controllers.refresh')
|
|||
|
||||
|
||||
class CheckState(enum.IntEnum):
|
||||
NOT_STARTED = enum.auto()
|
||||
CHECKING = enum.auto()
|
||||
FAILED = enum.auto()
|
||||
|
||||
UNKNOWN = enum.auto()
|
||||
AVAILABLE = enum.auto()
|
||||
UNAVAILABLE = enum.auto()
|
||||
|
||||
def is_definite(self):
|
||||
return self in [self.AVAILABLE, self.UNAVAILABLE]
|
||||
|
||||
|
||||
class RefreshController(BaseController):
|
||||
|
||||
|
@ -53,7 +47,6 @@ class RefreshController(BaseController):
|
|||
def __init__(self, app):
|
||||
super().__init__(app)
|
||||
self.snap_name = os.environ.get("SNAP_NAME", "subiquity")
|
||||
self.check_state = CheckState.NOT_STARTED
|
||||
self.configure_task = None
|
||||
self.check_task = None
|
||||
|
||||
|
@ -64,8 +57,20 @@ class RefreshController(BaseController):
|
|||
|
||||
def start(self):
|
||||
self.configure_task = schedule_task(self.configure_snapd())
|
||||
self.check_task = SingleInstanceTask(self.check_for_update)
|
||||
self.check_task.start_sync()
|
||||
self.check_task_starter = SingleInstanceTask(
|
||||
self.check_for_update, propagate_errors=False)
|
||||
self.check_task_starter.start_sync()
|
||||
|
||||
@property
|
||||
def check_state(self):
|
||||
task = self.check_task_starter.task
|
||||
if task is None:
|
||||
return CheckState.UNKNOWN
|
||||
if not task.done():
|
||||
return CheckState.UNKNOWN
|
||||
if task.exception():
|
||||
return CheckState.UNAVAILABLE
|
||||
return task.result()
|
||||
|
||||
async def configure_snapd(self):
|
||||
try:
|
||||
|
@ -124,35 +129,24 @@ class RefreshController(BaseController):
|
|||
return 'stable/ubuntu-' + release
|
||||
|
||||
def snapd_network_changed(self):
|
||||
if not self.check_state.is_definite():
|
||||
self.check_task.start_sync()
|
||||
if self.check_state == CheckState.UNKNOWN:
|
||||
self.check_task_starter.start_sync()
|
||||
|
||||
async def check_for_update(self):
|
||||
await self.configure_task
|
||||
# If we restarted into this version, don't check for a new version.
|
||||
if self.app.updated:
|
||||
self.check_state = CheckState.UNAVAILABLE
|
||||
return
|
||||
self.check_state = CheckState.CHECKING
|
||||
try:
|
||||
return CheckState.UNAVAILABLE
|
||||
result = await self.app.snapd.get('v2/find', select='refresh')
|
||||
except requests.exceptions.RequestException:
|
||||
log.exception("checking for update")
|
||||
self.check_state = CheckState.FAILED
|
||||
return
|
||||
log.debug("check_for_update received %s", result)
|
||||
for snap in result["result"]:
|
||||
if snap["name"] == self.snap_name:
|
||||
self.check_state = CheckState.AVAILABLE
|
||||
self.new_snap_version = snap["version"]
|
||||
log.debug(
|
||||
"new version of snap available: %r",
|
||||
self.new_snap_version)
|
||||
break
|
||||
else:
|
||||
self.check_state = CheckState.UNAVAILABLE
|
||||
if self.showing:
|
||||
self.ui.body.update_check_state()
|
||||
return CheckState.AVAILABLE
|
||||
return CheckState.UNAVAILABLE
|
||||
|
||||
async def start_update(self):
|
||||
update_marker = os.path.join(self.app.state_dir, 'updating')
|
||||
|
@ -178,8 +172,8 @@ class RefreshController(BaseController):
|
|||
self.offered_first_time = True
|
||||
elif index == 2:
|
||||
if not self.offered_first_time:
|
||||
if self.check_state in [CheckState.AVAILABLE,
|
||||
CheckState.CHECKING]:
|
||||
if self.check_state in [CheckState.UNKNOWN,
|
||||
CheckState.AVAILABLE]:
|
||||
show = True
|
||||
else:
|
||||
raise AssertionError("unexpected index {}".format(index))
|
||||
|
|
|
@ -94,6 +94,18 @@ class TaskProgress(WidgetWrap):
|
|||
self.spinner.spin()
|
||||
|
||||
|
||||
def exc_message(exc):
|
||||
try:
|
||||
result = exc.response.json()
|
||||
except (AttributeError, json.decoder.JSONDecodeError):
|
||||
message = None
|
||||
else:
|
||||
message = result.get("result", {}).get("message")
|
||||
if message is not None:
|
||||
return message
|
||||
return"Unknown error: {}".format(exc)
|
||||
|
||||
|
||||
class RefreshView(BaseView):
|
||||
|
||||
checking_title = _("Checking for installer update...")
|
||||
|
@ -102,8 +114,8 @@ class RefreshView(BaseView):
|
|||
"installer is available."
|
||||
)
|
||||
|
||||
failed_title = _("Contacting the snap store failed")
|
||||
failed_excerpt = _(
|
||||
check_failed_title = _("Contacting the snap store failed")
|
||||
check_failed_excerpt = _(
|
||||
"Contacting the snap store failed:"
|
||||
)
|
||||
|
||||
|
@ -119,33 +131,22 @@ class RefreshView(BaseView):
|
|||
"installer will restart automatically when the download is complete."
|
||||
)
|
||||
|
||||
update_failed_title = _("Update failed")
|
||||
update_failed_excerpt = _(
|
||||
"Downloading and applying the update:"
|
||||
)
|
||||
|
||||
def __init__(self, controller):
|
||||
self.controller = controller
|
||||
self.spinner = Spinner(self.controller.loop, style="dots")
|
||||
|
||||
if self.controller.check_state == CheckState.CHECKING:
|
||||
if self.controller.check_state == CheckState.UNKNOWN:
|
||||
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))
|
||||
self.check_state_available()
|
||||
|
||||
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()
|
||||
|
||||
|
@ -159,6 +160,36 @@ class RefreshView(BaseView):
|
|||
self.title = self.checking_title
|
||||
self.controller.ui.set_header(self.title)
|
||||
self._w = screen(rows, buttons, excerpt=_(self.checking_excerpt))
|
||||
schedule_task(self._wait_check_result())
|
||||
|
||||
async def _wait_check_result(self):
|
||||
try:
|
||||
check_state = await self.controller.check_task.task
|
||||
except Exception as e:
|
||||
self.check_state_failed(e)
|
||||
if check_state == CheckState.AVAILABLE:
|
||||
self.check_state_available()
|
||||
else:
|
||||
self.done()
|
||||
|
||||
def check_state_failed(self, exc):
|
||||
self.spinner.stop()
|
||||
|
||||
rows = [Text(exc_message(exc))]
|
||||
|
||||
buttons = button_pile([
|
||||
done_btn(_("Try again"), on_press=self.try_check_again),
|
||||
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 try_check_again(self, sender=None):
|
||||
self.controller.snapd_network_changed()
|
||||
self.check_state_checking()
|
||||
|
||||
def check_state_available(self, sender=None):
|
||||
self.spinner.stop()
|
||||
|
@ -191,34 +222,6 @@ class RefreshView(BaseView):
|
|||
self.controller.ui.set_header(self.available_title)
|
||||
self._w = screen(rows, buttons, excerpt=excerpt)
|
||||
|
||||
def check_state_failed(self, exc):
|
||||
self.spinner.stop()
|
||||
|
||||
try:
|
||||
result = exc.response.json()
|
||||
except (AttributeError, json.decoder.JSONDecodeError):
|
||||
message = None
|
||||
else:
|
||||
message = result.get("result", {}).get("message")
|
||||
if message is None:
|
||||
message = "Unknown error: {}".format(exc)
|
||||
|
||||
rows = [Text(message)]
|
||||
|
||||
buttons = button_pile([
|
||||
done_btn(_("Try again"), on_press=self.try_again),
|
||||
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 try_again(self, sender=None):
|
||||
self.controller.snapd_network_changed()
|
||||
self.check_state_checking()
|
||||
|
||||
def update(self, sender=None):
|
||||
self.spinner.stop()
|
||||
|
||||
|
@ -238,7 +241,7 @@ class RefreshView(BaseView):
|
|||
try:
|
||||
change_id = await self.controller.start_update()
|
||||
except requests.exceptions.RequestException as e:
|
||||
self.check_state_failed(e)
|
||||
self.update_failed(exc_message(e))
|
||||
return
|
||||
while True:
|
||||
change = await self.controller.get_progress(change_id)
|
||||
|
@ -247,9 +250,30 @@ class RefreshView(BaseView):
|
|||
# getting restarted by snapd...
|
||||
self.done()
|
||||
return
|
||||
if change['status'] not in ['Do', 'Doing']:
|
||||
self.update_failed(change.get('err', "Unknown error"))
|
||||
return
|
||||
self.update_progress(change)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
def try_update_again(self, sender=None):
|
||||
self.check_state_available()
|
||||
|
||||
def update_failed(self, msg):
|
||||
self.spinner.stop()
|
||||
|
||||
rows = [Text(msg)]
|
||||
|
||||
buttons = button_pile([
|
||||
done_btn(_("Try again"), on_press=self.try_update_again),
|
||||
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.update_failed_title
|
||||
self._w = screen(rows, buttons, excerpt=_(self.update_failed_excerpt))
|
||||
|
||||
def update_progress(self, change):
|
||||
for task in change['tasks']:
|
||||
tid = task['id']
|
||||
|
|
Loading…
Reference in New Issue