class InviteReceiver(QObject): # Wormhole got_welcome = Signal(dict) # got_code = Signal(str) got_introduction = Signal() got_message = Signal(dict) closed = Signal() # SetupRunner grid_already_joined = Signal(str) update_progress = Signal(str) got_icon = Signal(str) client_started = Signal(object) joined_folders = Signal(list) done = Signal(object) def __init__(self, known_gateways=None, use_tor=False): super(InviteReceiver, self).__init__() self.known_gateways = known_gateways self.use_tor = use_tor self.setup_runner = SetupRunner(known_gateways, use_tor) self.setup_runner.grid_already_joined.connect( self.grid_already_joined.emit ) self.setup_runner.update_progress.connect(self.update_progress.emit) self.setup_runner.got_icon.connect(self.got_icon.emit) self.setup_runner.client_started.connect(self.client_started.emit) self.setup_runner.joined_folders.connect(self.joined_folders.emit) self.setup_runner.done.connect(self.done.emit) self.wormhole = Wormhole(use_tor) self.wormhole.got_welcome.connect(self.got_welcome.emit) self.wormhole.got_introduction.connect(self.got_introduction.emit) self.wormhole.got_message.connect(self.got_message.emit) self.wormhole.closed.connect(self.closed.emit) def cancel(self): self.wormhole.close() @inlineCallbacks def _run_setup(self, settings, from_wormhole): settings = validate_settings( settings, self.known_gateways, None, from_wormhole ) yield self.setup_runner.run(settings) @inlineCallbacks def receive(self, code, settings=None): # TODO: Calculate/emit total steps if settings: yield self._run_setup(settings, from_wormhole=False) elif code.split("-")[0] == "0": settings = load_settings_from_cheatcode(code[2:]) if settings: yield self._run_setup(settings, from_wormhole=False) else: settings = yield self.wormhole.receive(code) yield self._run_setup(settings, from_wormhole=True)
def test_wormhole_close_emit_closed_signal_with_wormhole_error_pass(qtbot): wormhole = Wormhole() wormhole._wormhole = MagicMock() wormhole._wormhole.close = MagicMock(side_effect=WormholeError()) with qtbot.wait_signal(wormhole.closed) as blocker: yield wormhole.close() assert blocker.args == []
class InviteReceiver(QDialog): done = pyqtSignal(QWidget) closed = pyqtSignal(QWidget) def __init__(self, gateways): super(InviteReceiver, self).__init__() self.gateways = gateways self.wormhole = None self.setup_runner = None self.joined_folders = [] self.use_tor = False self.setMinimumSize(500, 300) self.mail_closed_icon = QLabel() self.mail_closed_icon.setPixmap( QPixmap(resource('mail-envelope-closed.png')).scaled(128, 128)) self.mail_closed_icon.setAlignment(Qt.AlignCenter) self.mail_open_icon = QLabel() self.mail_open_icon.setPixmap( QPixmap(resource('mail-envelope-open.png')).scaled(128, 128)) self.mail_open_icon.setAlignment(Qt.AlignCenter) self.folder_icon = QLabel() icon = QFileIconProvider().icon(QFileInfo(config_dir)) self.folder_icon.setPixmap(icon.pixmap(128, 128)) self.folder_icon.setAlignment(Qt.AlignCenter) self.invite_code_widget = InviteCodeWidget(self) self.label = self.invite_code_widget.label self.tor_checkbox = self.invite_code_widget.checkbox self.tor_checkbox.stateChanged.connect(self.on_checkbox_state_changed) self.lineedit = self.invite_code_widget.lineedit self.lineedit.error.connect(self.show_error) self.lineedit.go.connect(self.go) 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.progressbar = QProgressBar(self) self.progressbar.setValue(0) self.progressbar.setMaximum(6) # XXX self.progressbar.setTextVisible(False) self.message_label = QLabel() self.message_label.setStyleSheet("color: grey") self.message_label.setAlignment(Qt.AlignCenter) self.error_label = QLabel() self.error_label.setStyleSheet("color: red") self.error_label.setAlignment(Qt.AlignCenter) self.close_button = QPushButton("Close") self.close_button.clicked.connect(self.close) 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.addWidget(self.mail_closed_icon, 1, 2, 1, 3) layout.addWidget(self.mail_open_icon, 1, 2, 1, 3) layout.addWidget(self.folder_icon, 1, 2, 1, 3) layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 4) layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 5) layout.addWidget(self.label, 2, 3, 1, 1) layout.addWidget(self.tor_label, 3, 1, 1, 1, Qt.AlignRight | Qt.AlignVCenter) layout.addWidget(self.lineedit, 3, 2, 1, 3) layout.addWidget(self.progressbar, 3, 2, 1, 3) layout.addWidget(self.tor_checkbox, 4, 2, 1, 3, Qt.AlignCenter) layout.addWidget(self.message_label, 5, 1, 1, 5) layout.addWidget(self.error_label, 5, 2, 1, 3) layout.addWidget(self.close_button, 6, 3) layout.addItem(QSpacerItem(0, 0, 0, QSizePolicy.Expanding), 7, 1) self.reset() def reset(self): self.mail_open_icon.hide() self.folder_icon.hide() self.mail_closed_icon.show() self.label.setText("Enter invite code:") self.lineedit.show() self.lineedit.setText('') self.tor_checkbox.show() self.progressbar.hide() self.message_label.setText( "Invite codes can be used to join a grid or a folder") self.error_label.setText('') self.error_label.hide() self.close_button.hide() self.tor_label.hide() def on_checkbox_state_changed(self, state): self.use_tor = bool(state) logging.debug("use_tor=%s", self.use_tor) if state: self.progressbar.setStyleSheet( 'QProgressBar::chunk {{ background-color: {}; }}'.format( TOR_PURPLE)) else: self.progressbar.setStyleSheet('') def show_error(self, text): self.error_label.setText(text) self.message_label.hide() self.error_label.show() reactor.callLater(3, self.error_label.hide) reactor.callLater(3, self.message_label.show) def update_progress(self, message): step = self.progressbar.value() + 1 self.progressbar.setValue(step) self.message_label.setText(message) if step == 3: self.mail_closed_icon.hide() self.mail_open_icon.show() if step == 4: self.mail_open_icon.hide() self.folder_icon.show() def set_joined_folders(self, folders): self.joined_folders = folders def on_done(self, _): self.progressbar.setValue(self.progressbar.maximum()) self.close_button.show() self.done.emit(self) self.label.setPixmap( QPixmap(resource('green_checkmark.png')).scaled(32, 32)) if self.joined_folders and len(self.joined_folders) == 1: target = self.joined_folders[0] self.message_label.setText( 'Successfully joined folder "{0}"!\n"{0}" is now available ' 'for download'.format(target)) elif self.joined_folders: target = humanized_list(self.joined_folders, 'folders') self.message_label.setText( 'Successfully joined {0}!\n{0} are now available for ' 'download'.format(target)) def on_grid_already_joined(self, grid_name): QMessageBox.information( self, "Already connected", 'You are already connected to "{}"'.format(grid_name)) self.close() def got_message(self, message): self.update_progress("Reading invitation...") # 3 message = validate_settings(message, self.gateways, self) self.setup_runner = SetupRunner(self.gateways, self.use_tor) if not message.get('magic-folders'): self.setup_runner.grid_already_joined.connect( self.on_grid_already_joined) self.setup_runner.update_progress.connect(self.update_progress) self.setup_runner.joined_folders.connect(self.set_joined_folders) self.setup_runner.done.connect(self.on_done) self.setup_runner.run(message) def got_welcome(self): self.update_progress("Connected; waiting for message...") # 2 def handle_failure(self, failure): logging.error(str(failure)) if failure.type == CancelledError and self.progressbar.value() > 2: return show_failure(failure, self) self.close() def go(self, code): self.reset() self.label.setText(' ') self.lineedit.hide() self.tor_checkbox.hide() self.progressbar.show() if self.use_tor: self.tor_label.show() self.update_progress("Verifying invitation...") # 1 if code.split('-')[0] == "0": settings = get_settings_from_cheatcode(code[2:]) if settings: self.got_message(settings) return self.wormhole = Wormhole(self.use_tor) self.wormhole.got_welcome.connect(self.got_welcome) self.wormhole.got_message.connect(self.got_message) d = self.wormhole.receive(code) d.addErrback(self.handle_failure) reactor.callLater(30, d.cancel) def enterEvent(self, event): event.accept() self.lineedit.update_action_button() def closeEvent(self, event): event.accept() try: self.wormhole.close() except AttributeError: pass self.closed.emit(self) def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.close()
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()
def test_wormhole_close_return_early_no_wormhole_no_emit_closed(qtbot): wormhole = Wormhole() with qtbot.assert_not_emitted(wormhole.closed): yield wormhole.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)