Esempio n. 1
0
    def __init__(self, gui):
        super(SetupForm, self).__init__()
        self.gui = gui
        self.gateway = None
        self.setup_runner = None
        self.resize(400, 500)
        self.setWindowTitle(APP_NAME)
        self.page_1 = CodeEntryWidget(self)
        self.page_2 = ProgressBarWidget()
        self.page_3 = TahoeConfigForm()

        self.addWidget(self.page_1)
        self.addWidget(self.page_2)
        self.addWidget(self.page_3)

        self.lineedit = self.page_1.lineedit
        self.cancel_button = self.page_2.cancel_button
        self.finish_button = self.page_2.finish_button
        self.buttonbox = self.page_3.buttonbox
        self.help = self.page_1.help

        self.shortcut_close = QShortcut(QKeySequence.Close, self)
        self.shortcut_close.activated.connect(self.close)

        self.shortcut_quit = QShortcut(QKeySequence.Quit, self)
        self.shortcut_quit.activated.connect(self.close)

        self.lineedit.go.connect(self.go)
        self.lineedit.error.connect(self.show_error)
        self.cancel_button.clicked.connect(self.cancel_button_clicked)
        self.finish_button.clicked.connect(self.finish_button_clicked)
        self.buttonbox.accepted.connect(self.on_accepted)
        self.buttonbox.rejected.connect(self.reset)
        self.help.linkActivated.connect(self.on_link_activated)
Esempio n. 2
0
    def __init__(self, gui, known_gateways=None):
        super(WelcomeDialog, self).__init__()
        self.gui = gui
        self.known_gateways = known_gateways
        self.gateway = None
        self.setup_runner = None
        self.recovery_key_importer = None
        self.use_tor = False
        self.prompt_to_export = True
        self.resize(400, 500)
        self.setWindowTitle(APP_NAME)
        self.page_1 = WelcomeWidget(self)
        self.page_2 = ProgressBarWidget()
        self.page_3 = TahoeConfigForm()

        self.addWidget(self.page_1)
        self.addWidget(self.page_2)
        self.addWidget(self.page_3)

        self.lineedit = self.page_1.lineedit
        self.tor_checkbox = self.page_1.invite_code_widget.tor_checkbox
        self.restore_link = self.page_1.restore_link
        self.configure_link = self.page_1.configure_link
        self.preferences_button = self.page_1.preferences_button

        self.progressbar = self.page_2.progressbar
        self.cancel_button = self.page_2.cancel_button
        self.finish_button = self.page_2.finish_button

        self.buttonbox = self.page_3.buttonbox

        self.shortcut_close = QShortcut(QKeySequence.Close, self)
        self.shortcut_close.activated.connect(self.close)

        self.shortcut_quit = QShortcut(QKeySequence.Quit, self)
        self.shortcut_quit.activated.connect(self.close)

        self.lineedit.go.connect(self.go)
        self.lineedit.error.connect(self.show_error)
        self.restore_link.linkActivated.connect(self.on_restore_link_activated)
        self.configure_link.linkActivated.connect(
            self.on_configure_link_activated)
        self.preferences_button.clicked.connect(
            self.gui.show_preferences_window)

        self.cancel_button.clicked.connect(self.cancel_button_clicked)
        self.finish_button.clicked.connect(self.finish_button_clicked)

        self.buttonbox.accepted.connect(self.on_accepted)
        self.buttonbox.rejected.connect(self.reset)
Esempio n. 3
0
class WelcomeDialog(QStackedWidget):
    def __init__(self, gui, known_gateways=None):
        super(WelcomeDialog, self).__init__()
        self.gui = gui
        self.known_gateways = known_gateways
        self.gateway = None
        self.setup_runner = None
        self.recovery_key_importer = None
        self.use_tor = False
        self.prompt_to_export = True
        self.resize(400, 500)
        self.setWindowTitle(APP_NAME)
        self.page_1 = WelcomeWidget(self)
        self.page_2 = ProgressBarWidget()
        self.page_3 = TahoeConfigForm()

        self.addWidget(self.page_1)
        self.addWidget(self.page_2)
        self.addWidget(self.page_3)

        self.lineedit = self.page_1.lineedit
        self.checkbox = self.page_1.invite_code_widget.checkbox
        self.cancel_button = self.page_2.cancel_button
        self.finish_button = self.page_2.finish_button
        self.buttonbox = self.page_3.buttonbox
        self.restore_link = self.page_1.restore_link
        self.configure_link = self.page_1.configure_link
        self.preferences_button = self.page_1.preferences_button

        self.shortcut_close = QShortcut(QKeySequence.Close, self)
        self.shortcut_close.activated.connect(self.close)

        self.shortcut_quit = QShortcut(QKeySequence.Quit, self)
        self.shortcut_quit.activated.connect(self.close)

        self.lineedit.go.connect(self.go)
        self.lineedit.error.connect(self.show_error)
        self.checkbox.stateChanged.connect(self.on_checkbox_state_changed)
        self.cancel_button.clicked.connect(self.cancel_button_clicked)
        self.finish_button.clicked.connect(self.finish_button_clicked)
        self.buttonbox.accepted.connect(self.on_accepted)
        self.buttonbox.rejected.connect(self.reset)
        self.restore_link.linkActivated.connect(self.on_restore_link_activated)
        self.configure_link.linkActivated.connect(
            self.on_configure_link_activated)
        self.preferences_button.clicked.connect(
            self.gui.show_preferences_window)

    def on_checkbox_state_changed(self, state):
        self.use_tor = bool(state)
        log.debug("use_tor=%s", self.use_tor)
        if state:
            self.page_2.tor_label.show()
            self.page_2.progressbar.setStyleSheet(
                'QProgressBar::chunk { background-color: #7D4698; }')
        else:
            self.page_2.tor_label.hide()
            self.page_2.progressbar.setStyleSheet('')

    def on_configure_link_activated(self):
        self.setCurrentIndex(2)

    def update_progress(self, message):
        self.page_2.update_progress(message)

    def show_error(self, message):
        self.page_1.show_error(message)

    def reset(self):
        self.page_1.reset()
        self.page_2.reset()
        self.page_3.reset()
        self.setCurrentIndex(0)

    def load_service_icon(self, filepath):
        pixmap = QPixmap(filepath).scaled(100, 100)
        self.page_2.icon_overlay.setPixmap(pixmap)

    def handle_failure(self, failure):
        log.error(str(failure))
        if failure.type == CancelledError:
            if self.page_2.progressbar.value() <= 2:
                show_failure(failure, self)
                self.show_error("Invite timed out")
                self.reset()
            return
        show_failure(failure, self)
        if failure.type == ServerConnectionError:
            self.show_error("Server connection error")
        if failure.type == WelcomeError:
            self.show_error("Invite refused")
        elif failure.type == WrongPasswordError:
            self.show_error("Invite confirmation failed")
        elif failure.type == UpgradeRequiredError:
            self.show_error("Upgrade required")
        else:
            self.show_error(str(failure.type.__name__))
        self.reset()

    def on_done(self, gateway):
        self.gateway = gateway
        self.page_2.progressbar.setValue(self.page_2.progressbar.maximum())
        self.finish_button.show()

    def on_already_joined(self, grid_name):
        QMessageBox.information(
            self, "Already connected",
            'You are already connected to "{}"'.format(grid_name))
        self.close()

    def verify_settings(self, settings, from_wormhole=True):
        self.show()
        self.raise_()
        settings = validate_settings(settings, self.known_gateways, self,
                                     from_wormhole)
        self.setup_runner = SetupRunner(self.known_gateways, self.use_tor)
        steps = self.setup_runner.calculate_total_steps(settings) + 2
        self.page_2.progressbar.setMaximum(steps)
        self.setup_runner.grid_already_joined.connect(self.on_already_joined)
        self.setup_runner.update_progress.connect(self.update_progress)
        self.setup_runner.got_icon.connect(self.load_service_icon)
        self.setup_runner.client_started.connect(
            lambda gateway: self.gui.populate([gateway]))
        self.setup_runner.done.connect(self.on_done)
        d = self.setup_runner.run(settings)
        d.addErrback(self.handle_failure)

    def on_import_done(self, settings):
        if settings.get('hide-ip'):
            self.on_checkbox_state_changed(1)  # Toggle Tor checkbox "on"
        self.setCurrentIndex(1)
        self.page_2.progressbar.setValue(1)
        self.update_progress('Verifying invitation code...')
        self.prompt_to_export = False
        self.verify_settings(settings, from_wormhole=False)

    def on_restore_link_activated(self):
        self.recovery_key_importer = RecoveryKeyImporter(self.page_1)
        self.recovery_key_importer.done.connect(self.on_import_done)
        self.recovery_key_importer.do_import()

    def go(self, code):
        self.setCurrentIndex(1)
        self.page_2.progressbar.setValue(1)
        self.update_progress('Verifying invitation code...')
        if code.split('-')[0] == "0":
            settings = get_settings_from_cheatcode(code[2:])
            if settings:
                self.verify_settings(settings)
                return
        d = wormhole_receive(code, self.use_tor)  # pylint: disable=assignment-from-no-return
        d.addCallback(self.verify_settings)
        d.addErrback(self.handle_failure)
        reactor.callLater(30, d.cancel)

    def cancel_button_clicked(self):
        if self.page_2.is_complete():
            self.finish_button_clicked()
            return
        msgbox = QMessageBox(self)
        msgbox.setIcon(QMessageBox.Question)
        msgbox.setWindowTitle("Cancel setup?")
        msgbox.setText("Are you sure you wish to cancel the setup process?")
        msgbox.setInformativeText(
            "If you cancel, you may need to obtain a new invite code.")
        msgbox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        msgbox.setDefaultButton(QMessageBox.No)
        if msgbox.exec_() == QMessageBox.Yes:
            self.reset()

    def on_accepted(self):
        settings = self.page_3.get_settings()
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle(APP_NAME)
        msg.setStandardButtons(QMessageBox.Ok)
        if not settings['nickname']:
            msg.setText("Please enter a name.")
            msg.exec_()
        elif not settings['introducer']:
            msg.setText("Please enter an Introducer fURL.")
            msg.exec_()
        elif not is_valid_furl(settings['introducer']):
            msg.setText("Please enter a valid Introducer fURL.")
            msg.exec_()
        else:
            self.setCurrentIndex(1)
            self.verify_settings(settings, from_wormhole=False)

    def prompt_for_export(self, gateway):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Warning)
        msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        msg.setDefaultButton(QMessageBox.Yes)
        button_export = msg.button(QMessageBox.Yes)
        button_export.setText("&Export...")
        button_skip = msg.button(QMessageBox.No)
        button_skip.setText("&Skip")
        msg.setWindowTitle("Export Recovery Key?")
        # "Now that {} is configured..."
        msg.setText(
            "Before uploading any folders to {}, it is recommended that you "
            "export a Recovery Key and store it in a safe location (such as "
            "an encrypted USB drive or password manager).".format(
                gateway.name))
        msg.setInformativeText(
            "{} does not have access to your folders, and cannot restore "
            "access to them. But with a Recovery Key, you can restore access "
            "to uploaded folders in case something goes wrong (e.g., hardware "
            "failure, accidental data-loss).<p><p><a href={}>More information."
            "..</a>".format(gateway.name,
                            global_settings['help']['recovery_url']))
        #msg.setText(
        #    "Before uploading any folders to {}, it is <b>strongly "
        #    "recommended</b> that you <i>export a Recovery Key</i> and store "
        #    "it in a safe and secure location (such as an encrypted USB drive)"
        #    ".<p><p>Possessing a Recovery Key will allow you to restore "
        #    "access to any of the folders you've uploaded to {} in the event "
        #    "that something goes wrong (e.g., hardware failure, accidental "
        #    "data-loss).".format(gateway.name, gateway.name))
        #msg.setDetailedText(
        #    "A 'Recovery Key' is a small file that contains enough information"
        #    " to re-establish a connection with your storage provider and "
        #    "restore your previously-uploaded folders. Because access to this "
        #    "file is sufficient to access to any of the the data you've "
        #    "stored, it is important that you keep this file safe and secure; "
        #    "do not share your Recovery Key with anybody!")
        reply = msg.exec_()
        if reply == QMessageBox.Yes:
            self.gui.main_window.export_recovery_key()  # XXX
        else:
            # TODO: Nag user; "Are you sure?"
            pass

    def finish_button_clicked(self):
        self.gui.show()
        self.close()
        if self.prompt_to_export:
            self.prompt_for_export(self.gateway)
        self.reset()

    def enterEvent(self, event):
        event.accept()
        self.lineedit.update_action_button()

    def closeEvent(self, event):
        if self.gui.main_window.gateways:
            event.accept()
        else:
            event.ignore()
            msgbox = QMessageBox(self)
            msgbox.setIcon(QMessageBox.Question)
            msgbox.setWindowTitle("Exit setup?")
            msgbox.setText("Are you sure you wish to exit?")
            msgbox.setInformativeText(
                "{} has not yet been configured.".format(APP_NAME))
            msgbox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            msgbox.setDefaultButton(QMessageBox.No)
            if msgbox.exec_() == QMessageBox.Yes:
                QCoreApplication.instance().quit()
Esempio n. 4
0
class SetupForm(QStackedWidget):
    def __init__(self, gui):
        super(SetupForm, self).__init__()
        self.gui = gui
        self.gateway = None
        self.setup_runner = None
        self.resize(400, 500)
        self.setWindowTitle(APP_NAME)
        self.page_1 = CodeEntryWidget(self)
        self.page_2 = ProgressBarWidget()
        self.page_3 = TahoeConfigForm()

        self.addWidget(self.page_1)
        self.addWidget(self.page_2)
        self.addWidget(self.page_3)

        self.lineedit = self.page_1.lineedit
        self.cancel_button = self.page_2.cancel_button
        self.finish_button = self.page_2.finish_button
        self.buttonbox = self.page_3.buttonbox
        self.help = self.page_1.help

        self.shortcut_close = QShortcut(QKeySequence.Close, self)
        self.shortcut_close.activated.connect(self.close)

        self.shortcut_quit = QShortcut(QKeySequence.Quit, self)
        self.shortcut_quit.activated.connect(self.close)

        self.lineedit.go.connect(self.go)
        self.lineedit.error.connect(self.show_error)
        self.cancel_button.clicked.connect(self.cancel_button_clicked)
        self.finish_button.clicked.connect(self.finish_button_clicked)
        self.buttonbox.accepted.connect(self.on_accepted)
        self.buttonbox.rejected.connect(self.reset)
        self.help.linkActivated.connect(self.on_link_activated)

    def on_link_activated(self):
        self.setCurrentIndex(2)

    def update_progress(self, step, message):
        self.page_2.update_progress(step, message)

    def show_error(self, message):
        self.page_1.show_error(message)

    def reset(self):
        self.page_1.reset()
        self.page_2.reset()
        self.page_3.reset()
        self.setCurrentIndex(0)

    def load_service_icon(self, filepath):
        pixmap = QPixmap(filepath).scaled(100, 100)
        self.page_2.icon_overlay.setPixmap(pixmap)

    def show_failure(self, failure):
        log.error(str(failure))
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Warning)
        msg.setStandardButtons(QMessageBox.Retry)
        msg.setEscapeButton(QMessageBox.Retry)
        msg.setDetailedText(str(failure))
        if failure.type == WelcomeError:
            self.show_error("Invite refused")
            msg.setWindowTitle("Invite refused")
            msg.setText(
                "The server negotiating your invitation is online but is "
                "currently refusing to process any invitations. This may "
                "indicate that your version of {} is out-of-date, in which "
                "case you should upgrade to the latest version and try again.".
                format(APP_NAME))
        elif failure.type == WrongPasswordError:
            self.show_error("Invite confirmation failed")
            msg.setWindowTitle("Invite confirmation failed")
            msg.setText(
                "Either you mistyped your invite code or a potential "
                "attacker tried to guess your code and failed. To try "
                "again, you will need to obtain a new invite code from "
                "your inviter.")  # or "service provider"?
        elif failure.type == json.decoder.JSONDecodeError:
            self.show_error("Invalid response")
            msg.setIcon(QMessageBox.Critical)
            msg.setWindowTitle("Invalid response")
            msg.setText(
                "Your invite code worked but your inviter did not provide "
                "the information needed to complete the invitation process. "
                "Please let them know about the error, and try again later "
                "with a new invite code.")
        elif failure.type == CancelledError and self.page_2.step == 1:
            self.show_error("Invite timed out")
            msg.setWindowTitle("Invite timed out")
            msg.setText(
                "The invitation process has timed out. Your invite code may "
                "have expired. Please request a new invite code from your "
                "inviter and try again.")
        elif failure.type == UpgradeRequiredError:
            self.show_error("Upgrade required")
            msg.setWindowTitle("Upgrade required")
            msg.setText(
                "Your version of {} is out-of-date. Please upgrade to the "
                "latest version and try again with a new invite code.".format(
                    APP_NAME))
            msg.setIcon(QMessageBox.Critical)
            msg.setStandardButtons(QMessageBox.Ok)
        elif failure.type == ServerConnectionError:
            self.show_error("Server Connection Error")
            msg.setWindowTitle("Server Connection Error")
            msg.setText(
                "An error occured while connecting to the server. This could "
                "mean that the server is currently down or that there is some "
                "other problem with your connection. Please try again later.")
        else:
            self.show_error(str(failure.type.__name__))
            msg.setWindowTitle(str(failure.type.__name__))
            msg.setText(str(failure.value))
        msg.exec_()
        self.reset()

    def on_done(self, gateway):
        self.gateway = gateway
        self.gui.populate([gateway])
        self.finish_button.show()

    def verify_settings(self, settings):
        nickname = settings['nickname']
        while os.path.isdir(os.path.join(config_dir, nickname)):
            title = "{} - Choose a name".format(APP_NAME)
            label = "Please choose a different name for this connection:"
            if nickname:
                label = '{} is already connected to "{}".\n\n{}'.format(
                    APP_NAME, nickname, label)
            nickname, _ = QInputDialog.getText(self, title, label, 0, nickname)
        settings['nickname'] = nickname
        self.setup_runner = Setup()
        self.setup_runner.update_progress.connect(self.update_progress)
        self.setup_runner.got_icon.connect(self.load_service_icon)
        self.setup_runner.done.connect(self.on_done)
        d = self.setup_runner.run(settings)
        d.addErrback(self.show_failure)

    def go(self, code):
        self.setCurrentIndex(1)
        self.update_progress(1, 'Verifying invitation code...')
        d = wormhole_receive(code)
        d.addCallback(self.verify_settings)
        d.addErrback(self.show_failure)
        reactor.callLater(60, d.cancel)

    def cancel_button_clicked(self):
        if self.page_2.is_complete():
            self.finish_button_clicked()
            return
        reply = QMessageBox.question(
            self, "Cancel setup?",
            "Are you sure you wish to cancel the {} setup process? "
            "If you do, you may need to obtain a new invite code.".format(
                APP_NAME), QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.reset()

    def on_accepted(self):
        settings = self.page_3.get_settings()
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle(APP_NAME)
        msg.setStandardButtons(QMessageBox.Ok)
        if not settings['nickname']:
            msg.setText("Please enter a name.")
            msg.exec_()
        elif not settings['introducer']:
            msg.setText("Please enter an Introducer fURL.")
            msg.exec_()
        elif not is_valid_furl(settings['introducer']):
            msg.setText("Please enter a valid Introducer fURL.")
            msg.exec_()
        else:
            self.setCurrentIndex(1)
            self.verify_settings(settings)

    def prompt_for_export(self, gateway):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Warning)
        msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        msg.setDefaultButton(QMessageBox.Yes)
        button_export = msg.button(QMessageBox.Yes)
        button_export.setText("Export...")
        button_skip = msg.button(QMessageBox.No)
        button_skip.setText("Skip")
        msg.setWindowTitle("Export Recovery Key?")
        # "Now that {} is configured..."
        msg.setText(
            "Before uploading any folders to {}, it is <b>strongly "
            "recommended</b> that you <i>export a Recovery Key</i> and store "
            "it in a safe and secure location (such as an encrypted USB drive)"
            ".<p><p>Possessing a Recovery Key will allow you to restore "
            "access to any of the folders you've uploaded to {} in the event "
            "that something goes wrong (e.g., hardware failure, accidental "
            "data-loss).".format(gateway.name, gateway.name))
        msg.setDetailedText(
            "A 'Recovery Key' is a small file that contains enough information"
            " (a Tahoe-LAFS 'Introducer fURL' and 'rootcap') to re-establish "
            "a connection with your storage provider and restore your "
            "previously-uploaded folders. Because access to this file is "
            "sufficient to access to any of the the data you've stored, it is "
            "important that you keep this file safe and secure; do not share "
            "your Recovery Key with anybody!")
        reply = msg.exec_()
        if reply == QMessageBox.Yes:
            self.gui.main_window.export_recovery_key()  # XXX
        else:
            # TODO: Nag user; "Are you sure?"
            pass

    def finish_button_clicked(self):
        self.gui.show()
        self.close()
        self.prompt_for_export(self.gateway)
        self.reset()

    def closeEvent(self, event):
        if self.gui.main_window.gateways:
            event.accept()
        else:
            event.ignore()
            reply = QMessageBox.question(
                self, "Exit setup?", "{} has not yet been configured. "
                "Are you sure you wish to exit?".format(APP_NAME),
                QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.Yes:
                reactor.stop()
Esempio n. 5
0
def test_tahoe_config_form_get_settings():
    widget = TahoeConfigForm()
    widget.connection_settings.introducer_text_edit.setPlainText('test')
    assert widget.get_settings()['introducer'] == 'test'
Esempio n. 6
0
class WelcomeDialog(QStackedWidget):
    def __init__(self, gui, known_gateways=None):
        super(WelcomeDialog, self).__init__()
        self.gui = gui
        self.known_gateways = known_gateways
        self.gateway = None
        self.setup_runner = None
        self.resize(400, 500)
        self.setWindowTitle(APP_NAME)
        self.page_1 = WelcomeWidget(self)
        self.page_2 = ProgressBarWidget()
        self.page_3 = TahoeConfigForm()
        self.page_4 = PreferencesWidget()

        self.addWidget(self.page_1)
        self.addWidget(self.page_2)
        self.addWidget(self.page_3)
        self.addWidget(self.page_4)

        self.lineedit = self.page_1.lineedit
        self.cancel_button = self.page_2.cancel_button
        self.finish_button = self.page_2.finish_button
        self.buttonbox = self.page_3.buttonbox
        self.help = self.page_1.help
        self.preferences_button = self.page_1.preferences_button

        self.shortcut_close = QShortcut(QKeySequence.Close, self)
        self.shortcut_close.activated.connect(self.close)

        self.shortcut_quit = QShortcut(QKeySequence.Quit, self)
        self.shortcut_quit.activated.connect(self.close)

        self.lineedit.go.connect(self.go)
        self.lineedit.error.connect(self.show_error)
        self.cancel_button.clicked.connect(self.cancel_button_clicked)
        self.finish_button.clicked.connect(self.finish_button_clicked)
        self.buttonbox.accepted.connect(self.on_accepted)
        self.buttonbox.rejected.connect(self.reset)
        self.help.linkActivated.connect(self.on_link_activated)
        self.preferences_button.clicked.connect(
            self.on_preferences_button_clicked)
        self.page_4.accepted.connect(self.on_preferences_accepted)

    def on_link_activated(self):
        self.setCurrentIndex(2)

    def on_preferences_button_clicked(self):
        self.setCurrentIndex(3)

    def on_preferences_accepted(self):
        self.setCurrentIndex(0)

    def update_progress(self, message):
        self.page_2.update_progress(message)

    def show_error(self, message):
        self.page_1.show_error(message)

    def reset(self):
        self.page_1.reset()
        self.page_2.reset()
        self.page_3.reset()
        self.setCurrentIndex(0)

    def load_service_icon(self, filepath):
        pixmap = QPixmap(filepath).scaled(100, 100)
        self.page_2.icon_overlay.setPixmap(pixmap)

    def handle_failure(self, failure):
        log.error(str(failure))
        if failure.type == CancelledError:
            if self.page_2.progressbar.value() <= 2:
                show_failure(failure, self)
                self.show_error("Invite timed out")
                self.reset()
            return
        show_failure(failure, self)
        if failure.type == ServerConnectionError:
            self.show_error("Server connection error")
        if failure.type == WelcomeError:
            self.show_error("Invite refused")
        elif failure.type == WrongPasswordError:
            self.show_error("Invite confirmation failed")
        elif failure.type == UpgradeRequiredError:
            self.show_error("Upgrade required")
        else:
            self.show_error(str(failure.type.__name__))
        self.reset()

    def on_done(self, gateway):
        self.gateway = gateway
        self.gui.populate([gateway])
        self.page_2.progressbar.setValue(self.page_2.progressbar.maximum())
        self.finish_button.show()

    def on_already_joined(self, grid_name):
        QMessageBox.information(
            self, "Already connected",
            'You are already connected to "{}"'.format(grid_name))
        self.close()

    def verify_settings(self, settings, from_wormhole=True):
        settings = validate_settings(settings, self.known_gateways, self,
                                     from_wormhole)
        self.setup_runner = SetupRunner(self.known_gateways)
        steps = self.setup_runner.calculate_total_steps(settings) + 2
        self.page_2.progressbar.setMaximum(steps)
        self.setup_runner.grid_already_joined.connect(self.on_already_joined)
        self.setup_runner.update_progress.connect(self.update_progress)
        self.setup_runner.got_icon.connect(self.load_service_icon)
        self.setup_runner.done.connect(self.on_done)
        d = self.setup_runner.run(settings)
        d.addErrback(self.handle_failure)

    def go(self, code):
        self.setCurrentIndex(1)
        self.page_2.progressbar.setValue(1)
        self.update_progress('Verifying invitation code...')
        if code.split('-')[0] == "0":
            settings = get_settings_from_cheatcode(code[2:])
            if settings:
                self.verify_settings(settings)
                return
        d = wormhole_receive(code)
        d.addCallback(self.verify_settings)
        d.addErrback(self.handle_failure)
        reactor.callLater(30, d.cancel)

    def cancel_button_clicked(self):
        if self.page_2.is_complete():
            self.finish_button_clicked()
            return
        reply = QMessageBox.question(
            self, "Cancel setup?",
            "Are you sure you wish to cancel the {} setup process? "
            "If you do, you may need to obtain a new invite code.".format(
                APP_NAME), QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.reset()

    def on_accepted(self):
        settings = self.page_3.get_settings()
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle(APP_NAME)
        msg.setStandardButtons(QMessageBox.Ok)
        if not settings['nickname']:
            msg.setText("Please enter a name.")
            msg.exec_()
        elif not settings['introducer']:
            msg.setText("Please enter an Introducer fURL.")
            msg.exec_()
        elif not is_valid_furl(settings['introducer']):
            msg.setText("Please enter a valid Introducer fURL.")
            msg.exec_()
        else:
            self.setCurrentIndex(1)
            self.verify_settings(settings, from_wormhole=False)

    def prompt_for_export(self, gateway):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Warning)
        msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        msg.setDefaultButton(QMessageBox.Yes)
        button_export = msg.button(QMessageBox.Yes)
        button_export.setText("Export...")
        button_skip = msg.button(QMessageBox.No)
        button_skip.setText("Skip")
        msg.setWindowTitle("Export Recovery Key?")
        # "Now that {} is configured..."
        msg.setText(
            "Before uploading any folders to {}, it is <b>strongly "
            "recommended</b> that you <i>export a Recovery Key</i> and store "
            "it in a safe and secure location (such as an encrypted USB drive)"
            ".<p><p>Possessing a Recovery Key will allow you to restore "
            "access to any of the folders you've uploaded to {} in the event "
            "that something goes wrong (e.g., hardware failure, accidental "
            "data-loss).".format(gateway.name, gateway.name))
        msg.setDetailedText(
            "A 'Recovery Key' is a small file that contains enough information"
            " (a Tahoe-LAFS 'Introducer fURL' and 'rootcap') to re-establish "
            "a connection with your storage provider and restore your "
            "previously-uploaded folders. Because access to this file is "
            "sufficient to access to any of the the data you've stored, it is "
            "important that you keep this file safe and secure; do not share "
            "your Recovery Key with anybody!")
        reply = msg.exec_()
        if reply == QMessageBox.Yes:
            self.gui.main_window.export_recovery_key()  # XXX
        else:
            # TODO: Nag user; "Are you sure?"
            pass

    def finish_button_clicked(self):
        self.gui.show()
        self.close()
        self.prompt_for_export(self.gateway)
        self.reset()

    def closeEvent(self, event):
        if self.gui.main_window.gateways:
            event.accept()
        else:
            event.ignore()
            reply = QMessageBox.question(
                self, "Exit setup?", "{} has not yet been configured. "
                "Are you sure you wish to exit?".format(APP_NAME),
                QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.Yes:
                QCoreApplication.instance().quit()