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")
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")
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")
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")
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"]
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()
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)