示例#1
0
 def wait_load(self):
     spinner = Spinner(self.controller.app.aio_loop, style='dots')
     spinner.start()
     self._w = screen(
         [spinner], [ok_btn(label=_("Continue"), on_press=self.done)],
         excerpt=_("Loading server snaps from store, please wait..."))
     schedule_task(self._wait_load(spinner))
示例#2
0
    def __init__(self, app, report, interrupting=True):
        self.app = app
        self.report = report
        self.interrupting = interrupting

        self.btns = {
            'cancel': other_btn(
                _("Cancel upload"), on_press=self.cancel_upload),
            'close': close_btn(self, _("Close report")),
            'continue': close_btn(self, _("Continue")),
            'debug_shell': other_btn(
                _("Switch to a shell"), on_press=self.debug_shell),
            'restart': other_btn(
                _("Restart the installer"), on_press=self.restart),
            'submit': other_btn(
                _("Send to Canonical"), on_press=self.submit),
            'submitted': disabled(other_btn(_("Sent to Canonical"))),
            'view': other_btn(
                _("View full report"), on_press=self.view_report),
            }
        w = 0
        for n, b in self.btns.items():
            w = max(w, widget_width(b))
        for n, b in self.btns.items():
            self.btns[n] = Padding(b, width=w, align='center')

        self.spinner = Spinner(app.aio_loop, style='dots')
        self.pile = Pile([])
        self._report_changed()
        super().__init__("", [self.pile], 0, 0)
        connect_signal(self, 'closed', self.spinner.stop)
示例#3
0
class LoadingDialog(WidgetWrap):
    def __init__(self, parent, aio_loop, msg, task_to_cancel):
        self.parent = parent
        self.spinner = Spinner(aio_loop, style='dots')
        self.spinner.start()
        self.closed = False
        # | text |
        # 12    34
        self.width = len(msg) + 4
        widgets = [
            ('pack', Text(' ' + msg)),
            ('pack', self.spinner),
        ]
        if task_to_cancel is not None:
            self.task_to_cancel = task_to_cancel
            cancel = cancel_btn(label=_("Cancel"), on_press=self.close)
            widgets.append(('pack', button_pile([cancel])))
        super().__init__(LineBox(Pile(widgets)))

    def close(self, sender=None):
        if self.closed:
            return
        if sender is not None:
            self.task_to_cancel.cancel()
        self.closed = True
        self.spinner.stop()
        self.parent.remove_overlay()
示例#4
0
class TaskProgress(WidgetWrap):
    def __init__(self):
        self.mode = "spinning"
        self.spinner = Spinner()
        self.label = Text("", wrap='clip')
        cols = Color.progress_incomplete(
            Columns([
                (1, Text("")),
                self.label,
                (1, Text("")),
                (1, self.spinner),
                (1, Text("")),
            ]))
        super().__init__(cols)

    def update(self, task):
        progress = task['progress']
        done = progress['done']
        total = progress['total']
        if total > 1:
            if self.mode == "spinning":
                bar = TaskProgressBar()
                self._w = bar
            else:
                bar = self._w
            bar.label = task['summary']
            bar.done = total
            bar.current = done
        else:
            self.label.set_text(task['summary'])
            self.spinner.spin()
示例#5
0
class FetchingInfo(WidgetWrap):
    def __init__(self, parent, snap, loop):
        self.parent = parent
        self.spinner = Spinner(loop, style='dots')
        self.spinner.start()
        self.closed = False
        text = _("Fetching info for {}").format(snap.name)
        # | text |
        # 12    34
        self.width = len(text) + 4
        cancel = cancel_btn(label=_("Cancel"), on_press=self.close)
        super().__init__(
            LineBox(
                Pile([
                    ('pack', Text(' ' + text)),
                    ('pack', self.spinner),
                    ('pack', button_pile([cancel])),
                ])))

    def close(self, sender=None):
        if self.closed:
            return
        self.closed = True
        self.spinner.stop()
        self.parent.remove_overlay()
示例#6
0
    def __init__(self, controller):
        self.controller = controller
        self.spinner = Spinner(self.controller.app.aio_loop, style="dots")

        if self.controller.status.availability == RefreshCheckState.UNKNOWN:
            self.check_state_checking()
        else:
            self.check_state_available()

        super().__init__(self._w)
示例#7
0
 def load(self, sender=None):
     t = self.controller.get_snap_list_task()
     if t.done():
         self.loaded()
         return
     spinner = Spinner(self.controller.app.aio_loop, style='dots')
     spinner.start()
     self._w = screen(
         [spinner], [ok_btn(label=_("Continue"), on_press=self.done)],
         excerpt=_("Loading server snaps from store, please wait..."))
     schedule_task(self._wait(t, spinner))
示例#8
0
 def event_start(self, context_id, context_parent_id, message):
     self.event_finish(context_parent_id)
     walker = self.event_listbox.base_widget.body
     spinner = Spinner(self.controller.app.aio_loop)
     spinner.start()
     new_line = Columns([
         ('pack', Text(message)),
         ('pack', spinner),
         ], dividechars=1)
     self.ongoing[context_id] = len(walker)
     self._add_line(self.event_listbox, new_line)
示例#9
0
 def __init__(self):
     self.mode = "spinning"
     self.spinner = Spinner()
     self.label = Text("", wrap='clip')
     cols = Color.progress_incomplete(Columns([
         (1, Text("")),
         self.label,
         (1, Text("")),
         (1, self.spinner),
         (1, Text("")),
         ]))
     super().__init__(cols)
示例#10
0
 def __init__(self, aio_loop):
     spinner = Spinner(aio_loop, style='dots')
     spinner.start()
     text = _("Applying config")
     # | text |
     # 12    34
     self.width = len(text) + 4
     super().__init__(
         LineBox(Pile([
             ('pack', Text(' ' + text)),
             ('pack', spinner),
         ])))
示例#11
0
 def __init__(self, controller):
     self.controller = controller
     self.spinner = Spinner(aio_loop=controller.app.aio_loop, style="dots")
     self.spinner.start()
     super().__init__(
         screen([
             Text(
                 _("The installer is probing for block devices to install "
                   "to. Please wait until it completes.")),
             Text(""),
             self.spinner,
         ], [other_btn(_("Back"), on_press=self.cancel)]))
示例#12
0
 def __init__(self, controller):
     self.controller = controller
     self.spinner = Spinner(loop=controller.loop, style="dots")
     self.spinner.start()
     super().__init__(
         screen([
             Text(
                 _("The installer is probing for block devices to install "
                   "to. Please wait until it completes.")),
             Text(""),
             self.spinner,
         ]))
示例#13
0
 def show_apply_spinner(self):
     s = Spinner(self.controller.loop)
     s.start()
     c = TablePile([
         TableRow([
             Text(_("Applying changes")),
             s,
             ]),
         ], align='center')
     self.bottom.contents[0:0] = [
         (c, self.bottom.options()),
         (Text(""), self.bottom.options()),
         ]
示例#14
0
 def event_start(self, context, message):
     self.event_finish(context.parent)
     walker = self.event_listbox.base_widget.body
     indent = context.full_name().count('/') - 2
     if context.get('is-install-context'):
         indent -= 1
     spinner = Spinner(self.controller.app.aio_loop)
     spinner.start()
     new_line = Columns([
         ('pack', Text('  ' * indent + message)),
         ('pack', spinner),
         ], dividechars=1)
     self.ongoing[context] = len(walker)
     self._add_line(self.event_listbox, new_line)
示例#15
0
    def __init__(self, controller):
        self.controller = controller
        self.spinner = Spinner(controller.loop)

        self.reboot_btn = Toggleable(
            ok_btn(_("Reboot Now"), on_press=self.reboot))
        self.exit_btn = cancel_btn(_("Exit To Shell"), on_press=self.quit)
        self.view_log_btn = other_btn(_("View full log"),
                                      on_press=self.view_log)

        self.event_listbox = ListBox()
        self.event_linebox = MyLineBox(self.event_listbox)
        self.event_buttons = button_pile([self.view_log_btn])
        event_body = [
            ('pack', Text("")),
            ('weight', 1, Padding.center_79(self.event_linebox, min_width=76)),
            ('pack', Text("")),
            ('pack', self.event_buttons),
            ('pack', Text("")),
        ]
        self.event_pile = Pile(event_body)

        self.log_listbox = ListBox()
        log_linebox = MyLineBox(self.log_listbox, _("Full installer output"))
        log_body = [
            ('weight', 1, log_linebox),
            ('pack',
             button_pile([other_btn(_("Close"), on_press=self.close_log)])),
        ]
        self.log_pile = Pile(log_body)

        super().__init__(self.event_pile)
示例#16
0
 def __init__(self, parent):
     self.parent = parent
     spinner = Spinner(parent.controller.app.aio_loop, style='dots')
     spinner.start()
     text = _("Fetching SSH keys...")
     button = cancel_btn(label=_("Cancel"), on_press=self.cancel)
     # | text |
     # 12    34
     self.width = len(text) + 4
     super().__init__(
         LineBox(
             Pile([
                 ('pack', Text(' ' + text)),
                 ('pack', spinner),
                 ('pack', button_pile([button])),
                 ])))
示例#17
0
class SlowProbing(BaseView):

    title = _("Waiting for storage probing to complete")

    def __init__(self, controller):
        self.controller = controller
        self.spinner = Spinner(loop=controller.loop, style="dots")
        self.spinner.start()
        super().__init__(
            screen([
                Text(
                    _("The installer is probing for block devices to install "
                      "to. Please wait until it completes.")),
                Text(""),
                self.spinner,
            ]))
示例#18
0
 def __init__(self, parent, aio_loop, msg, task_to_cancel):
     self.parent = parent
     self.spinner = Spinner(aio_loop, style='dots')
     self.spinner.start()
     self.closed = False
     # | text |
     # 12    34
     self.width = len(msg) + 4
     widgets = [
         ('pack', Text(' ' + msg)),
         ('pack', self.spinner),
     ]
     if task_to_cancel is not None:
         self.task_to_cancel = task_to_cancel
         cancel = cancel_btn(label=_("Cancel"), on_press=self.close)
         widgets.append(('pack', button_pile([cancel])))
     super().__init__(LineBox(Pile(widgets)))
示例#19
0
 def __init__(self, parent, snap, loop):
     self.parent = parent
     self.spinner = Spinner(loop, style='dots')
     self.spinner.start()
     self.closed = False
     text = _("Fetching info for {}").format(snap.name)
     # | text |
     # 12    34
     self.width = len(text) + 4
     cancel = cancel_btn(label=_("Cancel"), on_press=self.close)
     super().__init__(
         LineBox(
             Pile([
                 ('pack', Text(' ' + text)),
                 ('pack', self.spinner),
                 ('pack', button_pile([cancel])),
             ])))
示例#20
0
 def _address_rows_for_device(self, dev):
     address_info = []
     dhcp_addresses = dev.dhcp_addresses()
     for v in 4, 6:
         if dev.dhcp_enabled(v):
             label = Text("DHCPv{v}".format(v=v))
             addrs = dhcp_addresses.get(v)
             if addrs:
                 address_info.extend([(label, Text(addr))
                                      for addr in addrs])
             elif dev.dhcp_state(v) == "PENDING":
                 s = Spinner(self.controller.app.aio_loop, align='left')
                 s.rate = 0.3
                 s.start()
                 address_info.append((label, s))
             elif dev.dhcp_state(v) == "TIMEDOUT":
                 address_info.append((label, Text(_("timed out"))))
             elif dev.dhcp_state(v) == "RECONFIGURE":
                 address_info.append((label, Text("-")))
             else:
                 address_info.append((label,
                                      Text(
                                          _("unknown state {state}".format(
                                              state=dev.dhcp_state(v))))))
         else:
             addrs = []
             for ip in dev.config.get('addresses', []):
                 if addr_version(ip) == v:
                     addrs.append(str(ip))
             if addrs:
                 address_info.append(
                     # Network addressing mode (static/dhcp/disabled)
                     (Text(_('static')), Text(', '.join(addrs))))
     if len(address_info) == 0:
         # Do not show an interface as disabled if it is part of a bond or
         # has a vlan on it.
         if not dev.is_used:
             reason = dev.disabled_reason
             if reason is None:
                 reason = ""
             # Network addressing mode (static/dhcp/disabled)
             address_info.append((Text(_("disabled")), Text(reason)))
     rows = []
     for label, value in address_info:
         rows.append(TableRow([Text(""), label, (2, value)]))
     return rows
示例#21
0
    def __init__(self, app, ref, interrupting=True):
        self.app = app
        self.error_ref = ref
        self.report = app.error_reporter.get(ref)
        self.pending = None
        if self.report is None:
            self.app.aio_loop.create_task(self._wait())
        else:
            connect_signal(self.report, 'changed', self._report_changed)
            self.report.mark_seen()
        self.interrupting = interrupting
        self.min_wait = self.app.aio_loop.create_task(asyncio.sleep(0.1))

        self.btns = {
            'cancel':
            other_btn(_("Cancel upload"), on_press=self.cancel_upload),
            'close':
            close_btn(self, _("Close report")),
            'continue':
            close_btn(self, _("Continue")),
            'debug_shell':
            other_btn(_("Switch to a shell"), on_press=self.debug_shell),
            'restart':
            other_btn(_("Restart the installer"), on_press=self.restart),
            'submit':
            other_btn(_("Send to Canonical"), on_press=self.submit),
            'submitted':
            disabled(other_btn(_("Sent to Canonical"))),
            'view':
            other_btn(_("View full report"), on_press=self.view_report),
        }
        w = 0
        for n, b in self.btns.items():
            w = max(w, widget_width(b))
        for n, b in self.btns.items():
            self.btns[n] = Padding(b, width=w, align='center')

        self.spinner = Spinner(app.aio_loop, style='dots')
        self.pile = Pile([])
        self.pile.contents[:] = [(w, self.pile.options('pack'))
                                 for w in self._pile_elements()]
        super().__init__("", [self.pile], 0, 0)
        connect_signal(self, 'closed', self.spinner.stop)
示例#22
0
class SlowProbing(BaseView):

    title = _("Waiting for storage probing to complete")

    def __init__(self, controller):
        self.controller = controller
        self.spinner = Spinner(aio_loop=controller.app.aio_loop, style="dots")
        self.spinner.start()
        super().__init__(
            screen([
                Text(
                    _("The installer is probing for block devices to install "
                      "to. Please wait until it completes.")),
                Text(""),
                self.spinner,
            ], [other_btn(_("Back"), on_press=self.cancel)]))

    def cancel(self, result=None):
        self.controller.cancel()
示例#23
0
    def load(self, sender=None):
        spinner = None
        called = False

        def callback(snap_list):
            nonlocal called
            called = True
            if spinner is not None:
                spinner.stop()
            if len(snap_list) == 0:
                self.offer_retry()
            else:
                self.make_main_screen(snap_list)
                self.show_main_screen()

        self.controller.get_snap_list(callback)
        if called:
            return
        spinner = Spinner(self.controller.loop, style='dots')
        spinner.start()
        self._w = screen(
            [spinner], [ok_btn(label=_("Continue"), on_press=self.done)],
            excerpt=_("Loading server snaps from store, please wait..."))
示例#24
0
 def _address_rows(self):
     address_info = []
     for v, dhcp_status, static_config in (
         (4, self.dev_info.dhcp4, self.dev_info.static4),
         (6, self.dev_info.dhcp6, self.dev_info.static6),
     ):
         if dhcp_status.enabled:
             label = Text("DHCPv{v}".format(v=v))
             addrs = dhcp_status.addresses
             if addrs:
                 address_info.extend([(label, Text(addr))
                                      for addr in addrs])
             elif dhcp_status.state == DHCPState.PENDING:
                 s = Spinner(self.parent.controller.app.aio_loop,
                             align='left')
                 s.rate = 0.3
                 s.start()
                 address_info.append((label, s))
             elif dhcp_status.state == DHCPState.TIMED_OUT:
                 address_info.append((label, Text(_("timed out"))))
             elif dhcp_status.state == DHCPState.RECONFIGURE:
                 address_info.append((label, Text("-")))
         elif static_config.addresses:
             address_info.append((
                 Text(_('static')),
                 Text(', '.join(static_config.addresses)),
             ))
     if len(address_info) == 0 and not self.dev_info.is_used:
         reason = self.dev_info.disabled_reason
         if reason is None:
             reason = ""
         address_info.append((Text(_("disabled")), Text(reason)))
     rows = []
     for label, value in address_info:
         rows.append(TableRow([Text(""), label, (2, value)]))
     return rows
示例#25
0
class ErrorReportStretchy(Stretchy):

    def __init__(self, app, report, interrupting=True):
        self.app = app
        self.report = report
        self.interrupting = interrupting

        self.btns = {
            'cancel': other_btn(
                _("Cancel upload"), on_press=self.cancel_upload),
            'close': close_btn(self, _("Close report")),
            'continue': close_btn(self, _("Continue")),
            'debug_shell': other_btn(
                _("Switch to a shell"), on_press=self.debug_shell),
            'restart': other_btn(
                _("Restart the installer"), on_press=self.restart),
            'submit': other_btn(
                _("Send to Canonical"), on_press=self.submit),
            'submitted': disabled(other_btn(_("Sent to Canonical"))),
            'view': other_btn(
                _("View full report"), on_press=self.view_report),
            }
        w = 0
        for n, b in self.btns.items():
            w = max(w, widget_width(b))
        for n, b in self.btns.items():
            self.btns[n] = Padding(b, width=w, align='center')

        self.spinner = Spinner(app.aio_loop, style='dots')
        self.pile = Pile([])
        self._report_changed()
        super().__init__("", [self.pile], 0, 0)
        connect_signal(self, 'closed', self.spinner.stop)

    def pb(self, upload):
        pb = ProgressBar(
            normal='progress_incomplete',
            complete='progress_complete',
            current=upload.bytes_sent,
            done=upload.bytes_to_send)

        def _progress():
            pb.done = upload.bytes_to_send
            pb.current = upload.bytes_sent
        connect_signal(upload, 'progress', _progress)

        return pb

    def _pile_elements(self):
        btns = self.btns.copy()

        widgets = [
            Text(rewrap(_(error_report_intros[self.report.kind]))),
            Text(""),
            ]

        self.spinner.stop()

        if self.report.state == ErrorReportState.DONE:
            widgets.append(btns['view'])
            widgets.append(Text(""))
            widgets.append(Text(rewrap(_(submit_text))))
            widgets.append(Text(""))

            if self.report.uploader:
                if self.upload_pb is None:
                    self.upload_pb = self.pb(self.report.uploader)
                widgets.append(self.upload_pb)
            else:
                if self.report.oops_id:
                    widgets.append(btns['submitted'])
                else:
                    widgets.append(btns['submit'])
                self.upload_pb = None

            fs_label, fs_loc = self.report.persistent_details
            if fs_label is not None:
                location_text = _(
                    "The error report has been saved to\n\n  {loc}\n\non the "
                    "filesystem with label {label!r}.").format(
                        loc=fs_loc, label=fs_label)
                widgets.extend([
                    Text(""),
                    Text(location_text),
                    ])
        else:
            text, spin = error_report_state_descriptions[self.report.state]
            widgets.append(Text(rewrap(_(text))))
            if spin:
                self.spinner.start()
                widgets.extend([
                    Text(""),
                    self.spinner])

        if self.report.uploader:
            widgets.extend([Text(""), btns['cancel']])
        elif self.interrupting:
            if self.report.state != ErrorReportState.INCOMPLETE:
                text, btn_names = error_report_options[self.report.kind]
                if text:
                    widgets.extend([Text(""), Text(rewrap(_(text)))])
                for b in btn_names:
                    widgets.extend([Text(""), btns[b]])
        else:
            widgets.extend([
                Text(""),
                btns['close'],
                ])

        return widgets

    def _report_changed(self):
        self.pile.contents[:] = [
            (w, self.pile.options('pack')) for w in self._pile_elements()]
        if self.pile.selectable():
            while not self.pile.focus.selectable():
                self.pile.focus_position += 1

    def debug_shell(self, sender):
        self.app.debug_shell()

    def restart(self, sender):
        self.app.restart()

    def view_report(self, sender):
        self.app.run_command_in_foreground(["less", self.report.path])

    def submit(self, sender):
        self.report.upload()

    def cancel_upload(self, sender):
        self.report.uploader.cancelled = True
        self.report.uploader = None
        self._report_changed()

    def opened(self):
        self.report.mark_seen()
        connect_signal(self.report, 'changed', self._report_changed)

    def closed(self):
        disconnect_signal(self.report, 'changed', self._report_changed)
示例#26
0
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.")

    check_failed_title = _("Contacting the snap store failed")
    check_failed_excerpt = _("Contacting the snap store failed:")

    available_title = _("Installer update available")
    available_excerpt = _(
        "Version {new} of the installer is now available ({current} is "
        "currently running).")

    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.")

    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.app.aio_loop, style="dots")

        if self.controller.status.availability == RefreshCheckState.UNKNOWN:
            self.check_state_checking()
        else:
            self.check_state_available()

        super().__init__(self._w)

    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))
        schedule_task(self._wait_check_result())

    async def _wait_check_result(self):
        try:
            status = await self.controller.wait_for_check()
        except Exception as e:
            self.check_state_failed(e)
            return
        if status.availability == RefreshCheckState.AVAILABLE:
            self.check_state_available()
        elif self.controller.showing:
            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.check_failed_title
        self._w = screen(rows, buttons, excerpt=_(self.check_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()

        rows = [
            Text(_("You can read the release notes for each version at:")),
            Text(""),
            Text("https://github.com/CanonicalLtd/subiquity/releases",
                 align='center'),
            Text(""),
            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

        excerpt = _(self.available_excerpt).format(
            current=self.controller.status.current_snap_version,
            new=self.controller.status.new_snap_version)

        self.title = self.available_title
        self.controller.ui.set_header(self.available_title)
        self._w = screen(rows, buttons, excerpt=excerpt)
        if 'update' in self.controller.answers:
            if self.controller.answers['update']:
                self.update()
            else:
                self.controller.app.aio_loop.call_soon(self.controller.done)

    def update(self, sender=None):
        self.spinner.stop()

        self.lb_tasks = ListBox([])
        self.task_to_bar = {}

        buttons = [
            other_btn(_("Cancel update"), on_press=self.check_state_available),
        ]

        self.controller.ui.set_header(_(self.progress_title))
        self._w = screen(self.lb_tasks,
                         buttons,
                         excerpt=_(self.progress_excerpt))
        schedule_task(self._update())

    async def _update(self):
        try:
            change_id = await self.controller.start_update()
        except aiohttp.ClientError as e:
            self.update_failed(exc_message(e))
            return
        while True:
            change = await self.controller.get_progress(change_id)
            if change['status'] == 'Done':
                # Clearly if we got here we didn't get restarted by
                # snapd/systemctl (dry-run mode or logged in via SSH)
                self.controller.app.restart(remove_last_screen=False)
                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']
            if task['status'] == "Done":
                bar = self.task_to_bar.get(tid)
                if bar is not None:
                    self.lb_tasks.base_widget.body.remove(bar)
                    del self.task_to_bar[tid]
            if task['status'] == "Doing":
                if tid not in self.task_to_bar:
                    self.task_to_bar[tid] = bar = TaskProgress()
                    self.lb_tasks.base_widget.body.append(bar)
                else:
                    bar = self.task_to_bar[tid]
                bar.update(task)

    def done(self, result=None):
        self.spinner.stop()
        self.controller.done()

    def cancel(self, result=None):
        self.spinner.stop()
        self.controller.cancel()