Esempio n. 1
0
def test_wormhole_send_raise_upgrade_required_no_abilities(qtbot, monkeypatch):
    fake_wormhole = MagicMock()
    fake_wormhole.get_welcome.return_value = {}
    fake_wormhole.get_message.return_value = b'{"blah": "blah"}'
    monkeypatch.setattr("gridsync.wormhole_.wormhole.create",
                        lambda x, y, z: fake_wormhole)
    wormhole = Wormhole()
    with pytest.raises(UpgradeRequiredError):
        yield wormhole.send("Testing", "123-test-test")
Esempio n. 2
0
def test_wormhole_send_raise_upgrade_required_bad_version(qtbot, monkeypatch):
    fake_wormhole = MagicMock()
    fake_wormhole.get_welcome.return_value = {}
    fake_wormhole.get_message.return_value = (
        b'{"abilities": {"server-v9999": {}}}')
    monkeypatch.setattr("gridsync.wormhole_.wormhole.create",
                        lambda x, y, z: fake_wormhole)
    wormhole = Wormhole()
    with pytest.raises(UpgradeRequiredError):
        yield wormhole.send("Testing", "123-test-test")
Esempio n. 3
0
def test_wormhole_send_emit_got_introduction_signal(qtbot, monkeypatch):
    fake_wormhole = MagicMock()
    fake_wormhole.get_welcome.return_value = {}
    fake_wormhole.get_message.return_value = (
        b'{"abilities": {"client-v1": {}}}')
    monkeypatch.setattr("gridsync.wormhole_.wormhole.create",
                        lambda x, y, z: fake_wormhole)
    wormhole = Wormhole()
    with qtbot.wait_signal(wormhole.got_introduction):
        yield wormhole.send("Testing", "123-test-test")
Esempio n. 4
0
def test_wormhole_send_succeed_emit_send_completed_signal(qtbot, monkeypatch):
    fake_wormhole = MagicMock()
    fake_wormhole.get_welcome.return_value = {}
    fake_wormhole.get_message.return_value = (
        b'{"abilities": {"client-v1": {}}}')
    fake_wormhole.get_code.return_value = "9999-test-code"
    monkeypatch.setattr("gridsync.wormhole_.wormhole.create",
                        lambda x, y, z: fake_wormhole)
    wormhole = Wormhole()
    with qtbot.wait_signal(wormhole.send_completed):
        yield wormhole.send("Testing")
Esempio n. 5
0
def test_wormhole_send_allocate_code_emit_got_code_signal(qtbot, monkeypatch):
    fake_wormhole = MagicMock()
    fake_wormhole.get_welcome.return_value = {}
    fake_wormhole.get_message.return_value = (
        b'{"abilities": {"client-v1": {}}}')
    fake_wormhole.get_code.return_value = "9999-test-code"
    monkeypatch.setattr("gridsync.wormhole_.wormhole.create",
                        lambda x, y, z: fake_wormhole)
    wormhole = Wormhole()
    with qtbot.wait_signal(wormhole.got_code) as blocker:
        yield wormhole.send("Testing")
    assert blocker.args == ["9999-test-code"]
Esempio n. 6
0
class ShareWidget(QDialog):
    done = pyqtSignal(QWidget)
    closed = pyqtSignal(QWidget)

    def __init__(self,
                 gateway,
                 gui,
                 folder_names=None):  # noqa: max-complexity=11 XXX
        super(ShareWidget, self).__init__()
        self.gateway = gateway
        self.gui = gui
        self.folder_names = folder_names
        self.folder_names_humanized = humanized_list(folder_names, 'folders')
        self.settings = {}
        self.wormhole = None
        self.pending_invites = []
        self.use_tor = self.gateway.use_tor

        # XXX Temporary(?) workaround for font-scaling inconsistencies observed
        # during user-testing (wherein fonts on Windows on one laptop were
        # rendering especially large for some reason but were fine elsewhere)
        if sys.platform == 'win32':
            self.setMinimumSize(600, 400)
        else:
            self.setMinimumSize(500, 300)

        header_icon = QLabel(self)
        if self.folder_names:
            icon = QFileIconProvider().icon(
                QFileInfo(
                    self.gateway.get_magic_folder_directory(
                        self.folder_names[0])))
        else:
            icon = QIcon(os.path.join(gateway.nodedir, 'icon'))
            if not icon.availableSizes():
                icon = QIcon(resource('tahoe-lafs.png'))
        header_icon.setPixmap(icon.pixmap(50, 50))

        header_text = QLabel(self)
        if self.folder_names:
            header_text.setText(self.folder_names_humanized)
        else:
            header_text.setText(self.gateway.name)
        font = QFont()
        if sys.platform == 'darwin':
            font.setPointSize(22)
        else:
            font.setPointSize(18)
        header_text.setFont(font)
        header_text.setAlignment(Qt.AlignCenter)

        header_layout = QGridLayout()
        header_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1,
                              1)
        header_layout.addWidget(header_icon, 1, 2)
        header_layout.addWidget(header_text, 1, 3)
        header_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1,
                              4)

        self.subtext_label = QLabel(self)
        font = QFont()
        if sys.platform == 'darwin':
            font.setPointSize(13)
        else:
            font.setPointSize(10)
        self.subtext_label.setFont(font)
        self.subtext_label.setStyleSheet("color: grey")
        self.subtext_label.setWordWrap(True)
        self.subtext_label.setAlignment(Qt.AlignCenter)

        self.noise_label = QLabel()
        font = QFont()
        if sys.platform == 'darwin':
            font.setPointSize(20)
        else:
            font.setPointSize(16)
        font.setFamily("Courier")
        font.setStyleHint(QFont.Monospace)
        self.noise_label.setFont(font)
        self.noise_label.setStyleSheet("color: grey")

        self.noise_timer = QTimer()
        self.noise_timer.timeout.connect(
            lambda: self.noise_label.setText(b58encode(os.urandom(16))))
        self.noise_timer.start(75)

        self.code_label = QLabel()
        font = QFont()
        if sys.platform == 'darwin':
            font.setPointSize(22)
        else:
            font.setPointSize(18)
        self.code_label.setFont(font)
        self.code_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
        self.code_label.hide()

        self.box_title = QLabel(self)
        self.box_title.setAlignment(Qt.AlignCenter)
        font = QFont()
        if sys.platform == 'darwin':
            font.setPointSize(20)
        else:
            font.setPointSize(16)
        self.box_title.setFont(font)

        self.box = QGroupBox()
        self.box.setAlignment(Qt.AlignCenter)
        self.box.setStyleSheet('QGroupBox {font-size: 16px}')

        self.copy_button = QToolButton()
        self.copy_button.setIcon(QIcon(resource('copy.png')))
        self.copy_button.setToolTip("Copy to clipboard")
        self.copy_button.setStyleSheet('border: 0px; padding: 0px;')
        self.copy_button.hide()

        box_layout = QGridLayout(self.box)
        box_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 1)
        box_layout.addWidget(self.noise_label, 1, 2)
        box_layout.addWidget(self.code_label, 1, 3)
        box_layout.addWidget(self.copy_button, 1, 4)
        box_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 5)

        self.close_button = QPushButton("Close and cancel invite")
        self.close_button.setAutoDefault(False)

        self.checkmark = QLabel()
        self.checkmark.setPixmap(
            QPixmap(resource('green_checkmark.png')).scaled(32, 32))
        self.checkmark.setAlignment(Qt.AlignCenter)
        self.checkmark.hide()

        self.tor_label = QLabel()
        self.tor_label.setToolTip(
            "This connection is being routed through the Tor network.")
        self.tor_label.setPixmap(
            QPixmap(resource('tor-onion.png')).scaled(32, 32))
        self.tor_label.hide()

        self.progress_bar = QProgressBar()
        self.progress_bar.setMaximum(2)
        self.progress_bar.setTextVisible(False)
        self.progress_bar.hide()

        layout = QGridLayout(self)
        layout.addItem(QSpacerItem(0, 0, 0, QSizePolicy.Expanding), 0, 0)
        layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 1)
        layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 2)
        layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 3)
        layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 4)
        layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 5)
        layout.addLayout(header_layout, 1, 3)
        layout.addItem(QSpacerItem(0, 0, 0, QSizePolicy.Expanding), 2, 1)
        layout.addWidget(self.box_title, 3, 2, 1, 3)
        layout.addWidget(self.checkmark, 3, 3)
        layout.addWidget(self.tor_label, 4, 1, 1, 1,
                         Qt.AlignRight | Qt.AlignVCenter)
        layout.addWidget(self.box, 4, 2, 1, 3)
        layout.addWidget(self.progress_bar, 4, 2, 1, 3)
        layout.addWidget(self.subtext_label, 5, 2, 1, 3)
        layout.addItem(QSpacerItem(0, 0, 0, QSizePolicy.Expanding), 6, 1)
        layout.addWidget(self.close_button, 7, 3)
        layout.addItem(QSpacerItem(0, 0, 0, QSizePolicy.Expanding), 8, 1)

        self.copy_button.clicked.connect(self.on_copy_button_clicked)
        self.close_button.clicked.connect(self.close)

        self.set_box_title("Generating invite code...")

        if self.use_tor:
            self.tor_label.show()
            self.progress_bar.setStyleSheet(
                'QProgressBar::chunk {{ background-color: {}; }}'.format(
                    TOR_PURPLE))

        self.go()  # XXX

    def set_box_title(self, text):
        if sys.platform == 'darwin':
            self.box_title.setText(text)
            self.box_title.show()
        else:
            self.box.setTitle(text)

    def on_copy_button_clicked(self):
        code = self.code_label.text()
        for mode in get_clipboard_modes():
            set_clipboard_text(code, mode)
        self.subtext_label.setText(
            "Copied '{}' to clipboard!\n\n".format(code))

    def on_got_code(self, code):
        self.noise_timer.stop()
        self.noise_label.hide()
        self.set_box_title("Your invite code is:")
        self.code_label.setText(code)
        self.code_label.show()
        self.copy_button.show()
        if self.folder_names:
            if len(self.folder_names) == 1:
                abilities = 'download "{}" and modify its contents'.format(
                    self.folder_names[0])
            else:
                abilities = 'download {} and modify their contents'.format(
                    self.folder_names_humanized)
        else:
            abilities = 'connect to "{}" and upload new folders'.format(
                self.gateway.name)
        self.subtext_label.setText(
            "Entering this code on another device will allow it to {}.\n"
            "This code can only be used once.".format(abilities))

    def on_got_introduction(self):
        if sys.platform == 'darwin':
            self.box_title.hide()
        self.box.hide()
        self.progress_bar.show()
        self.progress_bar.setValue(1)
        self.subtext_label.setText("Connection established; sending invite...")

    def on_send_completed(self):
        self.box.hide()
        self.progress_bar.show()
        self.progress_bar.setValue(2)
        self.checkmark.show()
        self.close_button.setText("Finish")
        if self.folder_names:
            target = self.folder_names_humanized
        else:
            target = self.gateway.name
        text = "Your invitation to {} was accepted".format(target)
        self.subtext_label.setText("Invite successful!\n {} at {}".format(
            text,
            datetime.now().strftime('%H:%M')))
        if get_preference('notifications', 'invite') != 'false':
            self.gui.show_message("Invite successful", text)

        # XXX FIXME Quick and dirty hack for user-testing
        if self.folder_names:
            for view in self.gui.main_window.central_widget.views:
                if view.gateway.name == self.gateway.name:
                    for folder in self.folder_names:
                        # Add two members for now, in case the original folder
                        # was empty (in which case the original "syncing"
                        # operation would not have occured and thus "admin"'s
                        # membership would not have been detected).
                        # FIXME Force call a Monitor.do_remote_scan() instead?
                        view.model().add_member(folder, None)
                        view.model().add_member(folder, None)
                        view.model().set_status_shared(folder)

    def handle_failure(self, failure):
        if failure.type == wormhole.errors.LonelyError:
            return
        logging.error(str(failure))
        show_failure(failure, self)
        self.wormhole.close()
        self.close()

    @inlineCallbacks
    def get_folder_invite(self, folder):
        member_id = b58encode(os.urandom(8))
        try:
            code = yield self.gateway.magic_folder_invite(folder, member_id)
        except TahoeError as err:
            code = None
            self.wormhole.close()
            error(self, "Invite Error", str(err))
            self.close()
        return folder, member_id, code

    @inlineCallbacks
    def get_folder_invites(self):
        self.subtext_label.setText("Creating folder invite(s)...\n\n")
        folders_data = {}
        tasks = []
        for folder in self.folder_names:
            tasks.append(self.get_folder_invite(folder))
        results = yield gatherResults(tasks)
        for folder, member_id, code in results:
            folders_data[folder] = {'code': code}
            self.pending_invites.append((folder, member_id))
        return folders_data

    @inlineCallbacks
    def go(self):
        self.wormhole = Wormhole(self.use_tor)
        self.wormhole.got_code.connect(self.on_got_code)
        self.wormhole.got_introduction.connect(self.on_got_introduction)
        self.wormhole.send_completed.connect(self.on_send_completed)
        self.settings = self.gateway.get_settings()
        if self.folder_names:
            folders_data = yield self.get_folder_invites()
            self.settings['magic-folders'] = folders_data
        self.subtext_label.setText("Opening wormhole...\n\n")
        self.wormhole.send(self.settings).addErrback(self.handle_failure)

    def closeEvent(self, event):
        if self.code_label.text() and self.progress_bar.value() < 2:
            msg = QMessageBox(self)
            msg.setIcon(QMessageBox.Question)
            msg.setWindowTitle("Cancel invitation?")
            msg.setText(
                'Are you sure you wish to cancel the invitation to "{}"?'.
                format(self.gateway.name))
            msg.setInformativeText(
                'The invite code "{}" will no longer be valid.'.format(
                    self.code_label.text()))
            msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            msg.setDefaultButton(QMessageBox.No)
            if msg.exec_() == QMessageBox.Yes:
                self.wormhole.close()
                if self.folder_names:
                    for folder, member_id in self.pending_invites:
                        self.gateway.magic_folder_uninvite(folder, member_id)
                event.accept()
                self.closed.emit(self)
            else:
                event.ignore()
        else:
            event.accept()
            if self.noise_timer.isActive():
                self.noise_timer.stop()
            self.closed.emit(self)

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            self.close()
Esempio n. 7
0
class InviteSender(QObject):

    created_invite = Signal()

    # Wormhole
    got_welcome = Signal(dict)
    got_code = Signal(str)
    got_introduction = Signal()
    send_completed = Signal()
    closed = Signal()

    def __init__(self, use_tor=False):
        super(InviteSender, self).__init__()
        self.use_tor = use_tor

        self.wormhole = Wormhole(use_tor)
        self.wormhole.got_welcome.connect(self.got_welcome.emit)
        self.wormhole.got_code.connect(self.got_code.emit)
        self.wormhole.got_introduction.connect(self.got_introduction.emit)
        self.wormhole.send_completed.connect(self.send_completed.emit)
        self.wormhole.closed.connect(self.closed.emit)

        self._pending_invites = []
        self._gateway = None

    def cancel(self):
        self.wormhole.close()
        if self._pending_invites:
            for folder, member_id in self._pending_invites:
                self._gateway.magic_folder_uninvite(folder, member_id)

    @staticmethod
    @inlineCallbacks
    def _get_folder_invite(gateway, folder):
        member_id = b58encode(os.urandom(8))
        code = yield gateway.magic_folder_invite(folder, member_id)
        return folder, member_id, code

    @inlineCallbacks
    def _get_folder_invites(self, gateway, folders):
        folders_data = {}
        tasks = []
        for folder in folders:
            tasks.append(self._get_folder_invite(gateway, folder))
        results = yield DeferredList(tasks, consumeErrors=True)
        for success, result in results:
            if success:
                folder, member_id, code = result
                folders_data[folder] = {'code': code}
                self._pending_invites.append((folder, member_id))
            else:  # Failure
                raise result.type(result.value)
        return folders_data

    @inlineCallbacks
    def send(self, gateway, folders=None):
        settings = gateway.get_settings()
        if folders:
            self._gateway = gateway
            folders_data = yield self._get_folder_invites(gateway, folders)
            settings['magic-folders'] = folders_data
        self.created_invite.emit()
        yield self.wormhole.send(settings)