diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index e4269dc6..debd2657 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -17,13 +17,12 @@ 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 threading import time -import os +import threading from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings -from onionshare.common import Common, ShutdownTimer +from onionshare.common import ShutdownTimer from .server_status import ServerStatus from .onion_thread import OnionThread @@ -38,9 +37,9 @@ class Mode(QtWidgets.QWidget): starting_server_step2 = QtCore.pyqtSignal() starting_server_step3 = QtCore.pyqtSignal() starting_server_error = QtCore.pyqtSignal(str) - set_share_server_active = QtCore.pyqtSignal(bool) + set_server_active = QtCore.pyqtSignal(bool) - def __init__(self, common, qtapp, app, web, status_bar, server_share_status_label, system_tray, filenames=None): + def __init__(self, common, qtapp, app, web, status_bar, server_status_label, system_tray, filenames=None): super(Mode, self).__init__() self.common = common self.qtapp = qtapp @@ -48,13 +47,13 @@ class Mode(QtWidgets.QWidget): self.web = web self.status_bar = status_bar - self.server_share_status_label = server_share_status_label + self.server_status_label = server_status_label self.system_tray = system_tray self.filenames = filenames # Server status - self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, False) + self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web) self.server_status.server_started.connect(self.start_server) self.server_status.server_stopped.connect(self.stop_server) self.server_status.server_canceled.connect(self.cancel_server) @@ -77,7 +76,7 @@ class Mode(QtWidgets.QWidget): def init(self): """ - Add custom initialization of the mode here. + Add custom initialization here. """ pass @@ -92,17 +91,16 @@ class Mode(QtWidgets.QWidget): Start the onionshare server. This uses multiple threads to start the Tor onion server and the web app. """ - self.common.log('ShareMode', 'start_server') - - self.set_share_server_active.emit(True) + self.common.log('Mode', 'start_server') + + self.start_server_custom() + self.set_server_active.emit(True) self.app.set_stealth(self.common.settings.get('use_stealth')) - # Hide and reset the downloads if we have previously shared - self.downloads.reset_downloads() - self.reset_info_counters() + # Clear the status bar self.status_bar.clearMessage() - self.server_share_status_label.setText('') + self.server_status_label.setText('') # Reset web counters self.web.download_count = 0 @@ -128,64 +126,44 @@ class Mode(QtWidgets.QWidget): # wait for modules in thread to load, preventing a thread-related cx_Freeze crash time.sleep(0.2) - self.common.log('OnionshareGui', 'start_server', 'Starting an onion thread') + self.common.log('Mode', 'start_server', 'Starting an onion thread') self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self}) self.t.daemon = True self.t.start() + + def start_server_custom(self): + """ + Add custom initialization here. + """ + pass def start_server_step2(self): """ - Step 2 in starting the onionshare server. Zipping up files. + Step 2 in starting the onionshare server. """ - self.common.log('ShareMode', 'start_server_step2') + self.common.log('Mode', 'start_server_step2') - # add progress bar to the status bar, indicating the compressing 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.start_server_step2_custom() - self._zip_progress_bar.total_files_size = Mode._compute_total_size(self.filenames) - self.status_bar.insertWidget(0, self._zip_progress_bar) + # Nothing to do here. - # 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: - self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) - self.app.cleanup_filenames.append(self.web.zip_filename) - - # Only continue if the server hasn't been canceled - if self.server_status.status != self.server_status.STATUS_STOPPED: - self.starting_server_step3.emit() - 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() + # start_server_step2_custom has call these to move on: + # self.starting_server_step3.emit() + # self.start_server_finished.emit() + + def start_server_step2_custom(self): + """ + Add custom initialization here. + """ + pass def start_server_step3(self): """ - Step 3 in starting the onionshare server. This displays the large filesize - warning, if applicable. + Step 3 in starting the onionshare server. """ - self.common.log('ShareMode', 'start_server_step3') + self.common.log('Mode', '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 self.web.zip_filesize >= 157286400: # 150mb - self.filesize_warning.setText(strings._("large_filesize", True)) - self.filesize_warning.show() + self.start_server_step3_custom() if self.common.settings.get('shutdown_timeout'): # Convert the date value to seconds between now and then @@ -200,21 +178,31 @@ class Mode(QtWidgets.QWidget): self.stop_server() self.start_server_error(strings._('gui_server_started_after_timeout')) + def start_server_step3_custom(self): + """ + Add custom initialization here. + """ + pass + def start_server_error(self, error): """ If there's an error when trying to start the onion service """ - self.common.log('ShareMode', 'start_server_error') - - self.set_share_server_active.emit(False) + self.common.log('Mode', 'start_server_error') Alert(self.common, error, QtWidgets.QMessageBox.Warning) + self.set_server_active.emit(False) 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() + self.start_server_error_custom() + + def start_server_error_custom(self): + """ + Add custom initialization here. + """ + pass + def cancel_server(self): """ Cancel the server while it is preparing to start @@ -227,7 +215,7 @@ class Mode(QtWidgets.QWidget): """ Stop the onionshare server. """ - self.common.log('ShareMode', 'stop_server') + self.common.log('Mode', 'stop_server') if self.server_status.status != self.server_status.STATUS_STOPPED: try: @@ -237,81 +225,27 @@ class Mode(QtWidgets.QWidget): pass self.app.cleanup() - # Remove the progress bar - if self._zip_progress_bar is not None: - self.status_bar.removeWidget(self._zip_progress_bar) - self._zip_progress_bar = None + self.stop_server_custom() - 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_share_server_active.emit(False) + self.set_server_active.emit(False) self.stop_server_finished.emit() + + def stop_server_custom(self): + """ + Add custom initialization here. + """ + pass - @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 - - -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) + def handle_tor_broke(self): + """ + Handle connection from Tor breaking. + """ + if self.server_status.status != self.server_status.STATUS_STOPPED: + self.server_status.stop_server() + self.handle_tor_broke_custom() + + def handle_tor_broke_custom(self): + """ + Add custom initialization here. + """ + pass diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index f976013f..1aa38c1c 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -152,12 +152,12 @@ class OnionShareGui(QtWidgets.QMainWindow): self.setStatusBar(self.status_bar) # 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) + self.server_status_label = QtWidgets.QLabel('') + self.server_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; padding: 2px; }') + self.status_bar.insertWidget(0, self.server_status_label) # Share and receive mode widgets - self.share_mode = ShareMode(self.common, qtapp, app, web, self.status_bar, self.server_share_status_label, self.system_tray, filenames) + self.share_mode = ShareMode(self.common, qtapp, app, web, self.status_bar, self.server_status_label, self.system_tray, filenames) self.share_mode.init() self.share_mode.server_status.server_started.connect(self.update_server_status_indicator) self.share_mode.server_status.server_stopped.connect(self.update_server_status_indicator) @@ -168,8 +168,8 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.server_status.button_clicked.connect(self.clear_message) self.share_mode.server_status.url_copied.connect(self.copy_url) self.share_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) - self.share_mode.set_share_server_active.connect(self.set_share_server_active) - self.receive_mode = ReceiveMode(self.common, qtapp, app, web, self.status_bar, self.server_share_status_label, self.system_tray) + self.share_mode.set_server_active.connect(self.set_share_server_active) + self.receive_mode = ReceiveMode(self.common, qtapp, app, web, self.status_bar, self.server_status_label, self.system_tray) self.receive_mode.init() self.update_mode_switcher() diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index b25b728f..b7813170 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -44,3 +44,10 @@ class ReceiveMode(Mode): This method is called regularly on a timer while receive mode is active. """ pass + + def start_server_step2_custom(self): + """ + Step 2 in starting the server. Nothing to do here but move on to step 3. + """ + self.starting_server_step3.emit() + self.start_server_finished.emit() diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 54f54582..602af595 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -39,22 +39,18 @@ class ServerStatus(QtWidgets.QWidget): STATUS_WORKING = 1 STATUS_STARTED = 2 - def __init__(self, common, qtapp, app, web, share_mode, file_selection=None): + def __init__(self, common, qtapp, app, web, file_selection=None): super(ServerStatus, self).__init__() self.common = common self.status = self.STATUS_STOPPED + self.share_mode = False # Gets changed in in self.set_share_mode self.qtapp = qtapp self.app = app self.web = web - # Only used in share mode - self.share_mode = share_mode - if self.share_mode: - self.file_selection = file_selection - # Shutdown timeout layout self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) self.shutdown_timeout = QtWidgets.QDateTimeEdit() @@ -118,6 +114,13 @@ class ServerStatus(QtWidgets.QWidget): self.setLayout(layout) self.update() + + def set_share_mode(self, file_selection): + """ + The server status is in share mode. + """ + self.share_mode = True + self.file_selection = file_selection def shutdown_timeout_reset(self): """ diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 699e469b..0d05da06 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -17,10 +17,13 @@ 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 threading +import os from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings from onionshare.onion import * +from onionshare.common import Common from .file_selection import FileSelection from .downloads import Downloads @@ -43,6 +46,7 @@ class ShareMode(Mode): self.file_selection.file_list.add_file(filename) # Server status + self.server_status.set_share_mode(self.file_selection) self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_stopped.connect(self.file_selection.server_stopped) self.server_status.server_stopped.connect(self.update_primary_action) @@ -102,8 +106,8 @@ class ShareMode(Mode): self._zip_progress_bar = None # Layout - self.layout.insertWidget(1, self.info_widget) self.layout.insertLayout(0, self.file_selection) + self.layout.insertWidget(0, self.info_widget) # Always start with focus on file selection self.file_selection.setFocus() @@ -129,18 +133,98 @@ class ShareMode(Mode): if self.web.download_count == 0 or self.web.done: self.server_status.stop_server() self.status_bar.clearMessage() - self.server_share_status_label.setText(strings._('close_on_timeout', True)) + self.server_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)) + self.server_status_label.setText(strings._('timeout_download_still_running', True)) - def handle_tor_broke(self): + def start_server_custom(self): """ - Handle connection from Tor breaking. + Starting the server. + """ + # Hide and reset the downloads if we have previously shared + self.downloads.reset_downloads() + self.reset_info_counters() + + def start_server_step2_custom(self): + """ + Step 2 in starting the server. Zipping up files. + """ + # Add progress bar to the status bar, indicating the compressing 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 = ShareMode._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: + self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) + self.app.cleanup_filenames.append(self.web.zip_filename) + + # Only continue if the server hasn't been canceled + if self.server_status.status != self.server_status.STATUS_STOPPED: + self.starting_server_step3.emit() + 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_custom(self): + """ + Step 3 in starting the server. Remove zip progess bar, and display large filesize + warning, if applicable. + """ + # 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 self.web.zip_filesize >= 157286400: # 150mb + self.filesize_warning.setText(strings._("large_filesize", True)) + self.filesize_warning.show() + + def start_server_error_custom(self): + """ + Start server error. + """ + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + + def stop_server_custom(self): + """ + Stop server. + """ + # Remove the progress bar + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + + 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() + + def handle_tor_broke_custom(self): + """ + Connection to Tor broke. """ - if self.server_status.status != self.server_status.STATUS_STOPPED: - self.server_status.stop_server() self.primary_action.hide() self.info_widget.hide() @@ -190,7 +274,7 @@ class ShareMode(Mode): if not self.web.stay_open: self.server_status.stop_server() self.status_bar.clearMessage() - self.server_share_status_label.setText(strings._('closing_automatically', True)) + self.server_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"]) @@ -284,4 +368,69 @@ class ShareMode(Mode): self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress.png') self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.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)) \ No newline at end of file + self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count)) + + @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 + + +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)