# -*- coding: utf-8 -*- """ OnionShare | https://onionshare.org/ Copyright (C) 2018 Micah Lee This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import os, threading, time from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings, common, web from onionshare.settings import Settings from onionshare.onion import * from .tor_connection_dialog import TorConnectionDialog from .settings_dialog import SettingsDialog from .file_selection import FileSelection from .server_status import ServerStatus from .downloads import Downloads from .alert import Alert from .update_checker import UpdateThread class OnionShareGui(QtWidgets.QMainWindow): """ OnionShareGui is the main window for the GUI that contains all of the GUI elements. """ start_server_finished = QtCore.pyqtSignal() stop_server_finished = QtCore.pyqtSignal() starting_server_step2 = QtCore.pyqtSignal() starting_server_step3 = QtCore.pyqtSignal() starting_server_error = QtCore.pyqtSignal(str) def __init__(self, onion, qtapp, app, filenames, config=False): super(OnionShareGui, self).__init__() self._initSystemTray() common.log('OnionShareGui', '__init__') self.onion = onion self.qtapp = qtapp self.app = app self.setWindowTitle('OnionShare') self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) self.setMinimumWidth(430) # Load settings self.config = config self.settings = Settings(self.config) self.settings.load() # File selection self.file_selection = FileSelection() if filenames: for filename in filenames: self.file_selection.file_list.add_file(filename) # Server status self.server_status = ServerStatus(self.qtapp, self.app, web, self.file_selection, self.settings) self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_started.connect(self.start_server) self.server_status.server_started.connect(self.update_server_status_indicator) self.server_status.server_stopped.connect(self.file_selection.server_stopped) self.server_status.server_stopped.connect(self.stop_server) self.server_status.server_stopped.connect(self.update_server_status_indicator) self.server_status.server_stopped.connect(self.update_primary_action) self.server_status.server_canceled.connect(self.cancel_server) self.server_status.server_canceled.connect(self.file_selection.server_stopped) self.server_status.server_canceled.connect(self.update_primary_action) self.start_server_finished.connect(self.clear_message) self.start_server_finished.connect(self.server_status.start_server_finished) self.start_server_finished.connect(self.update_server_status_indicator) self.stop_server_finished.connect(self.server_status.stop_server_finished) self.stop_server_finished.connect(self.update_server_status_indicator) self.file_selection.file_list.files_updated.connect(self.server_status.update) self.file_selection.file_list.files_updated.connect(self.update_primary_action) self.server_status.url_copied.connect(self.copy_url) self.server_status.hidservauth_copied.connect(self.copy_hidservauth) self.starting_server_step2.connect(self.start_server_step2) self.starting_server_step3.connect(self.start_server_step3) self.starting_server_error.connect(self.start_server_error) self.server_status.button_clicked.connect(self.clear_message) # Filesize warning self.filesize_warning = QtWidgets.QLabel() self.filesize_warning.setWordWrap(True) self.filesize_warning.setStyleSheet('padding: 10px 0; font-weight: bold; color: #333333;') self.filesize_warning.hide() # Downloads self.downloads = Downloads() self.downloads_container = QtWidgets.QScrollArea() self.downloads_container.setWidget(self.downloads) self.downloads_container.setWidgetResizable(True) self.downloads_container.setMaximumHeight(200) self.downloads_container.setMinimumHeight(75) self.vbar = self.downloads_container.verticalScrollBar() self.downloads_container.hide() # downloads start out hidden self.new_download = False self.downloads_in_progress = 0 self.downloads_completed = 0 # Info label along top of screen self.info_layout = QtWidgets.QHBoxLayout() self.info_label = QtWidgets.QLabel() self.info_label.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') self.info_in_progress_downloads_count = QtWidgets.QLabel() self.info_in_progress_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') self.info_completed_downloads_count = QtWidgets.QLabel() self.info_completed_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') self.update_downloads_completed(self.downloads_in_progress) self.update_downloads_in_progress(self.downloads_in_progress) self.info_layout.addWidget(self.info_label) self.info_layout.addStretch() self.info_layout.addWidget(self.info_in_progress_downloads_count) self.info_layout.addWidget(self.info_completed_downloads_count) self.info_widget = QtWidgets.QWidget() self.info_widget.setLayout(self.info_layout) self.info_widget.hide() # Settings button on the status bar self.settings_button = QtWidgets.QPushButton() self.settings_button.setDefault(False) self.settings_button.setFlat(True) self.settings_button.setFixedWidth(40) self.settings_button.setIcon( QtGui.QIcon(common.get_resource_path('images/settings.png')) ) self.settings_button.clicked.connect(self.open_settings) # Server status indicator on the status bar self.server_status_image_stopped = QtGui.QImage(common.get_resource_path('images/server_stopped.png')) self.server_status_image_working = QtGui.QImage(common.get_resource_path('images/server_working.png')) self.server_status_image_started = QtGui.QImage(common.get_resource_path('images/server_started.png')) self.server_status_image_label = QtWidgets.QLabel() self.server_status_image_label.setFixedWidth(20) self.server_status_label = QtWidgets.QLabel() self.server_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; }') server_status_indicator_layout = QtWidgets.QHBoxLayout() server_status_indicator_layout.addWidget(self.server_status_image_label) server_status_indicator_layout.addWidget(self.server_status_label) self.server_status_indicator = QtWidgets.QWidget() self.server_status_indicator.setLayout(server_status_indicator_layout) self.update_server_status_indicator() # Status bar self.status_bar = QtWidgets.QStatusBar() self.status_bar.setSizeGripEnabled(False) statusBar_cssStyleData =""" QStatusBar { font-style: italic; color: #666666; } QStatusBar::item { border: 0px; }""" self.status_bar.setStyleSheet(statusBar_cssStyleData) self.status_bar.addPermanentWidget(self.server_status_indicator) self.status_bar.addPermanentWidget(self.settings_button) self.setStatusBar(self.status_bar) # Status bar, zip progress bar self._zip_progress_bar = None # Status bar, sharing messages self.server_share_status_label = QtWidgets.QLabel('') self.server_share_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; padding: 2px; }') self.status_bar.insertWidget(0, self.server_share_status_label) # Primary action layout primary_action_layout = QtWidgets.QVBoxLayout() primary_action_layout.addWidget(self.server_status) primary_action_layout.addWidget(self.filesize_warning) primary_action_layout.addWidget(self.downloads_container) self.primary_action = QtWidgets.QWidget() self.primary_action.setLayout(primary_action_layout) self.primary_action.hide() self.update_primary_action() # Main layout self.layout = QtWidgets.QVBoxLayout() self.layout.addWidget(self.info_widget) self.layout.addLayout(self.file_selection) self.layout.addWidget(self.primary_action) central_widget = QtWidgets.QWidget() central_widget.setLayout(self.layout) self.setCentralWidget(central_widget) self.show() # Always start with focus on file selection self.file_selection.setFocus() # The server isn't active yet self.set_server_active(False) # Create the timer self.timer = QtCore.QTimer() self.timer.timeout.connect(self.check_for_requests) # Start the "Connecting to Tor" dialog, which calls onion.connect() tor_con = TorConnectionDialog(self.qtapp, self.settings, self.onion) tor_con.canceled.connect(self._tor_connection_canceled) tor_con.open_settings.connect(self._tor_connection_open_settings) tor_con.start() # Start the timer self.timer.start(500) # After connecting to Tor, check for updates self.check_for_updates() def update_primary_action(self): # Show or hide primary action layout file_count = self.file_selection.file_list.count() if file_count > 0: self.primary_action.show() self.info_widget.show() # Update the file count in the info label total_size_bytes = 0 for index in range(self.file_selection.file_list.count()): item = self.file_selection.file_list.item(index) total_size_bytes += item.size_bytes total_size_readable = common.human_readable_filesize(total_size_bytes) if file_count > 1: self.info_label.setText(strings._('gui_file_info', True).format(file_count, total_size_readable)) else: self.info_label.setText(strings._('gui_file_info_single', True).format(file_count, total_size_readable)) else: self.primary_action.hide() self.info_widget.hide() # Resize window self.adjustSize() def update_server_status_indicator(self): common.log('OnionShareGui', 'update_server_status_indicator') # Set the status image if self.server_status.status == self.server_status.STATUS_STOPPED: self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) self.server_status_label.setText(strings._('gui_status_indicator_stopped', True)) elif self.server_status.status == self.server_status.STATUS_WORKING: self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) self.server_status_label.setText(strings._('gui_status_indicator_working', True)) elif self.server_status.status == self.server_status.STATUS_STARTED: self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) self.server_status_label.setText(strings._('gui_status_indicator_started', True)) def _initSystemTray(self): system = common.get_platform() menu = QtWidgets.QMenu() self.settingsAction = menu.addAction(strings._('gui_settings_window_title', True)) self.settingsAction.triggered.connect(self.open_settings) self.helpAction = menu.addAction(strings._('gui_settings_button_help', True)) self.helpAction.triggered.connect(SettingsDialog.help_clicked) self.exitAction = menu.addAction(strings._('systray_menu_exit', True)) self.exitAction.triggered.connect(self.close) self.systemTray = QtWidgets.QSystemTrayIcon(self) # The convention is Mac systray icons are always grayscale if system == 'Darwin': self.systemTray.setIcon(QtGui.QIcon(common.get_resource_path('images/logo_grayscale.png'))) else: self.systemTray.setIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) self.systemTray.setContextMenu(menu) self.systemTray.show() def _tor_connection_canceled(self): """ If the user cancels before Tor finishes connecting, ask if they want to quit, or open settings. """ common.log('OnionShareGui', '_tor_connection_canceled') def ask(): a = Alert(strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings', True)) quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit', True)) a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole) a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole) a.setDefaultButton(settings_button) a.exec_() if a.clickedButton() == settings_button: # Open settings common.log('OnionShareGui', '_tor_connection_canceled', 'Settings button clicked') self.open_settings() if a.clickedButton() == quit_button: # Quit common.log('OnionShareGui', '_tor_connection_canceled', 'Quit button clicked') # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.qtapp.quit) # Wait 100ms before asking QtCore.QTimer.singleShot(100, ask) def _tor_connection_open_settings(self): """ The TorConnectionDialog wants to open the Settings dialog """ common.log('OnionShareGui', '_tor_connection_open_settings') # Wait 1ms for the event loop to finish closing the TorConnectionDialog QtCore.QTimer.singleShot(1, self.open_settings) def open_settings(self): """ Open the SettingsDialog. """ common.log('OnionShareGui', 'open_settings') def reload_settings(): common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading') self.settings.load() # We might've stopped the main requests timer if a Tor connection failed. # If we've reloaded settings, we probably succeeded in obtaining a new # connection. If so, restart the timer. if self.onion.is_authenticated(): if not self.timer.isActive(): self.timer.start(500) # If there were some files listed for sharing, we should be ok to # re-enable the 'Start Sharing' button now. if self.server_status.file_selection.get_num_files() > 0: self.server_status.server_button.setEnabled(True) self.status_bar.clearMessage() # If we switched off the shutdown timeout setting, ensure the widget is hidden. if not self.settings.get('shutdown_timeout'): self.server_status.shutdown_timeout_container.hide() d = SettingsDialog(self.onion, self.qtapp, self.config) d.settings_saved.connect(reload_settings) d.exec_() # When settings close, refresh the server status UI self.server_status.update() def start_server(self): """ Start the onionshare server. This uses multiple threads to start the Tor onion server and the web app. """ common.log('OnionShareGui', 'start_server') self.set_server_active(True) self.app.set_stealth(self.settings.get('use_stealth')) # Hide and reset the downloads if we have previously shared self.downloads_container.hide() self.downloads.reset_downloads() self.reset_info_counters() self.status_bar.clearMessage() self.server_share_status_label.setText('') # Reset web counters web.download_count = 0 web.error404_count = 0 web.set_gui_mode() # start the onion service in a new thread def start_onion_service(self): try: self.app.start_onion_service() self.starting_server_step2.emit() except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e: self.starting_server_error.emit(e.args[0]) return self.app.stay_open = not self.settings.get('close_after_first_download') # start onionshare http service in new thread t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open, self.settings.get('slug'))) t.daemon = True t.start() # wait for modules in thread to load, preventing a thread-related cx_Freeze crash time.sleep(0.2) common.log('OnionshareGui', 'start_server', 'Starting an onion thread') self.t = OnionThread(function=start_onion_service, kwargs={'self': self}) self.t.daemon = True self.t.start() def start_server_step2(self): """ Step 2 in starting the onionshare server. Zipping up files. """ common.log('OnionShareGui', 'start_server_step2') # add progress bar to the status bar, indicating the crunching of files. self._zip_progress_bar = ZipProgressBar(0) self.filenames = [] for index in range(self.file_selection.file_list.count()): self.filenames.append(self.file_selection.file_list.item(index).filename) self._zip_progress_bar.total_files_size = OnionShareGui._compute_total_size(self.filenames) self.status_bar.insertWidget(0, self._zip_progress_bar) # prepare the files for sending in a new thread def finish_starting_server(self): # prepare files to share def _set_processed_size(x): if self._zip_progress_bar != None: self._zip_progress_bar.update_processed_size_signal.emit(x) try: web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) self.app.cleanup_filenames.append(web.zip_filename) self.starting_server_step3.emit() # done self.start_server_finished.emit() except OSError as e: self.starting_server_error.emit(e.strerror) return t = threading.Thread(target=finish_starting_server, kwargs={'self': self}) t.daemon = True t.start() def start_server_step3(self): """ Step 3 in starting the onionshare server. This displays the large filesize warning, if applicable. """ common.log('OnionShareGui', 'start_server_step3') # Remove zip progress bar if self._zip_progress_bar is not None: self.status_bar.removeWidget(self._zip_progress_bar) self._zip_progress_bar = None # warn about sending large files over Tor if web.zip_filesize >= 157286400: # 150mb self.filesize_warning.setText(strings._("large_filesize", True)) self.filesize_warning.show() if self.settings.get('shutdown_timeout'): # Convert the date value to seconds between now and then now = QtCore.QDateTime.currentDateTime() self.timeout = now.secsTo(self.server_status.timeout) # Set the shutdown timeout value if self.timeout > 0: self.app.shutdown_timer = common.close_after_seconds(self.timeout) self.app.shutdown_timer.start() # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start. else: self.stop_server() self.start_server_error(strings._('gui_server_started_after_timeout')) def start_server_error(self, error): """ If there's an error when trying to start the onion service """ common.log('OnionShareGui', 'start_server_error') self.set_server_active(False) Alert(error, QtWidgets.QMessageBox.Warning) self.server_status.stop_server() if self._zip_progress_bar is not None: self.status_bar.removeWidget(self._zip_progress_bar) self._zip_progress_bar = None self.status_bar.clearMessage() def cancel_server(self): """ Cancel the server while it is preparing to start """ if self.t: self.t.quit() self.stop_server() def stop_server(self): """ Stop the onionshare server. """ common.log('OnionShareGui', 'stop_server') if self.server_status.status != self.server_status.STATUS_STOPPED: try: web.stop(self.app.port) except: # Probably we had no port to begin with (Onion service didn't start) pass self.app.cleanup() # Remove ephemeral service, but don't disconnect from Tor self.onion.cleanup(stop_tor=False) self.filesize_warning.hide() self.downloads_in_progress = 0 self.downloads_completed = 0 self.update_downloads_in_progress(0) self.file_selection.file_list.adjustSize() self.set_server_active(False) self.stop_server_finished.emit() def check_for_updates(self): """ Check for updates in a new thread, if enabled. """ system = common.get_platform() if system == 'Windows' or system == 'Darwin': if self.settings.get('use_autoupdate'): def update_available(update_url, installed_version, latest_version): Alert(strings._("update_available", True).format(update_url, installed_version, latest_version)) self.update_thread = UpdateThread(self.onion, self.config) self.update_thread.update_available.connect(update_available) self.update_thread.start() @staticmethod def _compute_total_size(filenames): total_size = 0 for filename in filenames: if os.path.isfile(filename): total_size += os.path.getsize(filename) if os.path.isdir(filename): total_size += common.dir_size(filename) return total_size def check_for_requests(self): """ Check for messages communicated from the web app, and update the GUI accordingly. """ self.update() # Have we lost connection to Tor somehow? if not self.onion.is_authenticated(): self.timer.stop() if self.server_status.status != self.server_status.STATUS_STOPPED: self.server_status.stop_server() self.server_status.server_button.setEnabled(False) self.status_bar.showMessage(strings._('gui_tor_connection_lost', True)) if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True)) # scroll to the bottom of the dl progress bar log pane # if a new download has been added if self.new_download: self.vbar.setValue(self.vbar.maximum()) self.new_download = False events = [] done = False while not done: try: r = web.q.get(False) events.append(r) except web.queue.Empty: done = True for event in events: if event["type"] == web.REQUEST_LOAD: self.status_bar.showMessage(strings._('download_page_loaded', True)) elif event["type"] == web.REQUEST_DOWNLOAD: self.downloads_container.show() # show the downloads layout self.downloads.add_download(event["data"]["id"], web.zip_filesize) self.new_download = True self.downloads_in_progress += 1 self.update_downloads_in_progress(self.downloads_in_progress) if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) elif event["type"] == web.REQUEST_RATE_LIMIT: self.stop_server() Alert(strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) elif event["type"] == web.REQUEST_PROGRESS: self.downloads.update_download(event["data"]["id"], event["data"]["bytes"]) # is the download complete? if event["data"]["bytes"] == web.zip_filesize: if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True)) # Update the total 'completed downloads' info self.downloads_completed += 1 self.update_downloads_completed(self.downloads_completed) # Update the 'in progress downloads' info self.downloads_in_progress -= 1 self.update_downloads_in_progress(self.downloads_in_progress) # close on finish? if not web.get_stay_open(): self.server_status.stop_server() self.status_bar.clearMessage() self.server_share_status_label.setText(strings._('closing_automatically', True)) else: if self.server_status.status == self.server_status.STATUS_STOPPED: self.downloads.cancel_download(event["data"]["id"]) self.downloads_in_progress = 0 self.update_downloads_in_progress(self.downloads_in_progress) elif event["type"] == web.REQUEST_CANCELED: self.downloads.cancel_download(event["data"]["id"]) # Update the 'in progress downloads' info self.downloads_in_progress -= 1 self.update_downloads_in_progress(self.downloads_in_progress) if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) elif event["path"] != '/favicon.ico': self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(web.error404_count, strings._('other_page_loaded', True), event["path"])) # If the auto-shutdown timer has stopped, stop the server if self.server_status.status == self.server_status.STATUS_STARTED: if self.app.shutdown_timer and self.settings.get('shutdown_timeout'): if self.timeout > 0: now = QtCore.QDateTime.currentDateTime() seconds_remaining = now.secsTo(self.server_status.timeout) self.server_status.server_button.setText(strings._('gui_stop_server_shutdown_timeout', True).format(seconds_remaining)) if not self.app.shutdown_timer.is_alive(): # If there were no attempts to download the share, or all downloads are done, we can stop if web.download_count == 0 or web.done: self.server_status.stop_server() self.status_bar.clearMessage() self.server_share_status_label.setText(strings._('close_on_timeout', True)) # A download is probably still running - hold off on stopping the share else: self.status_bar.clearMessage() self.server_share_status_label.setText(strings._('timeout_download_still_running', True)) def copy_url(self): """ When the URL gets copied to the clipboard, display this in the status bar. """ common.log('OnionShareGui', 'copy_url') if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True)) def copy_hidservauth(self): """ When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar. """ common.log('OnionShareGui', 'copy_hidservauth') if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True)) def clear_message(self): """ Clear messages from the status bar. """ self.status_bar.clearMessage() def set_server_active(self, active): """ Disable the Settings button while an OnionShare server is active. """ if active: self.settings_button.hide() else: self.settings_button.show() # Disable settings menu action when server is active self.settingsAction.setEnabled(not active) def reset_info_counters(self): """ Set the info counters back to zero. """ self.update_downloads_completed(0) self.update_downloads_in_progress(0) def update_downloads_completed(self, count): """ Update the 'Downloads completed' info widget. """ if count == 0: self.info_completed_downloads_image = common.get_resource_path('images/download_completed_none.png') else: self.info_completed_downloads_image = common.get_resource_path('images/download_completed.png') self.info_completed_downloads_count.setText(' {1:d}'.format(self.info_completed_downloads_image, count)) self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(count)) def update_downloads_in_progress(self, count): """ Update the 'Downloads in progress' info widget. """ if count == 0: self.info_in_progress_downloads_image = common.get_resource_path('images/download_in_progress_none.png') else: self.info_in_progress_downloads_image = common.get_resource_path('images/download_in_progress.png') self.info_in_progress_downloads_count.setText(' {1:d}'.format(self.info_in_progress_downloads_image, count)) self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count)) def closeEvent(self, e): common.log('OnionShareGui', 'closeEvent') try: if self.server_status.status != self.server_status.STATUS_STOPPED: common.log('OnionShareGui', 'closeEvent, opening warning dialog') dialog = QtWidgets.QMessageBox() dialog.setWindowTitle(strings._('gui_quit_title', True)) dialog.setText(strings._('gui_quit_warning', True)) dialog.setIcon(QtWidgets.QMessageBox.Critical) quit_button = dialog.addButton(strings._('gui_quit_warning_quit', True), QtWidgets.QMessageBox.YesRole) dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit', True), QtWidgets.QMessageBox.NoRole) dialog.setDefaultButton(dont_quit_button) reply = dialog.exec_() # Quit if reply == 0: self.stop_server() e.accept() # Don't Quit else: e.ignore() except: e.accept() class ZipProgressBar(QtWidgets.QProgressBar): update_processed_size_signal = QtCore.pyqtSignal(int) def __init__(self, total_files_size): super(ZipProgressBar, self).__init__() self.setMaximumHeight(20) self.setMinimumWidth(200) self.setValue(0) self.setFormat(strings._('zip_progress_bar_format')) cssStyleData =""" QProgressBar { border: 1px solid #4e064f; background-color: #ffffff !important; text-align: center; color: #9b9b9b; } QProgressBar::chunk { border: 0px; background-color: #4e064f; width: 10px; }""" self.setStyleSheet(cssStyleData) self._total_files_size = total_files_size self._processed_size = 0 self.update_processed_size_signal.connect(self.update_processed_size) @property def total_files_size(self): return self._total_files_size @total_files_size.setter def total_files_size(self, val): self._total_files_size = val @property def processed_size(self): return self._processed_size @processed_size.setter def processed_size(self, val): self.update_processed_size(val) def update_processed_size(self, val): self._processed_size = val if self.processed_size < self.total_files_size: self.setValue(int((self.processed_size * 100) / self.total_files_size)) elif self.total_files_size != 0: self.setValue(100) else: self.setValue(0) class OnionThread(QtCore.QThread): """ A QThread for starting our Onion Service. By using QThread rather than threading.Thread, we are able to call quit() or terminate() on the startup if the user decided to cancel (in which case do not proceed with obtaining the Onion address and starting the web server). """ def __init__(self, function, kwargs=None): super(OnionThread, self).__init__() common.log('OnionThread', '__init__') self.function = function if not kwargs: self.kwargs = {} else: self.kwargs = kwargs def run(self): common.log('OnionThread', 'run') self.function(**self.kwargs)