diff options
author | Micah Lee <micah@micahflee.com> | 2019-10-13 14:35:40 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-10-13 14:35:40 -0400 |
commit | 896659c6963c14c4e047cff7f3d421977060ed23 (patch) | |
tree | 660468dd7478fb001ced211a4a33ae28a1b5f9a4 /onionshare_gui | |
parent | bbf6c02da645fcd4fc50db18dd341178ad4fa2e6 (diff) | |
parent | 7d24bf9ffcd1e21e087431c21bfc658803a61ab6 (diff) | |
download | onionshare-2.2.tar.gz onionshare-2.2.zip |
Merge pull request #1048 from micahflee/developv2.2
Version 2.2
Diffstat (limited to 'onionshare_gui')
-rw-r--r-- | onionshare_gui/__init__.py | 51 | ||||
-rw-r--r-- | onionshare_gui/mode/__init__.py | 161 | ||||
-rw-r--r-- | onionshare_gui/mode/file_selection.py (renamed from onionshare_gui/mode/share_mode/file_selection.py) | 93 | ||||
-rw-r--r-- | onionshare_gui/mode/history.py | 371 | ||||
-rw-r--r-- | onionshare_gui/mode/receive_mode/__init__.py | 107 | ||||
-rw-r--r-- | onionshare_gui/mode/share_mode/__init__.py | 92 | ||||
-rw-r--r-- | onionshare_gui/mode/share_mode/threads.py | 21 | ||||
-rw-r--r-- | onionshare_gui/mode/website_mode/__init__.py | 266 | ||||
-rw-r--r-- | onionshare_gui/onionshare_gui.py | 444 | ||||
-rw-r--r-- | onionshare_gui/server_status.py | 290 | ||||
-rw-r--r-- | onionshare_gui/settings_dialog.py | 918 | ||||
-rw-r--r-- | onionshare_gui/threads.py | 72 | ||||
-rw-r--r-- | onionshare_gui/tor_connection_dialog.py | 43 | ||||
-rw-r--r-- | onionshare_gui/update_checker.py | 98 | ||||
-rw-r--r-- | onionshare_gui/widgets.py | 22 |
15 files changed, 2297 insertions, 752 deletions
diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 99c52937..23d8dd3d 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -32,22 +32,26 @@ from onionshare.onionshare import OnionShare from .onionshare_gui import OnionShareGui + class Application(QtWidgets.QApplication): """ This is Qt's QApplication class. It has been overridden to support threads and the quick keyboard shortcut. """ + def __init__(self, common): - if common.platform == 'Linux' or common.platform == 'BSD': + if common.platform == "Linux" or common.platform == "BSD": self.setAttribute(QtCore.Qt.AA_X11InitThreads, True) QtWidgets.QApplication.__init__(self, sys.argv) self.installEventFilter(self) def eventFilter(self, obj, event): - if (event.type() == QtCore.QEvent.KeyPress and - event.key() == QtCore.Qt.Key_Q and - event.modifiers() == QtCore.Qt.ControlModifier): - self.quit() + if ( + event.type() == QtCore.QEvent.KeyPress + and event.key() == QtCore.Qt.Key_Q + and event.modifiers() == QtCore.Qt.ControlModifier + ): + self.quit() return False @@ -70,11 +74,34 @@ def main(): qtapp = Application(common) # Parse arguments - parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=48)) - parser.add_argument('--local-only', action='store_true', dest='local_only', help="Don't use Tor (only for development)") - parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help="Log OnionShare errors to stdout, and web errors to disk") - parser.add_argument('--filenames', metavar='filenames', nargs='+', help="List of files or folders to share") - parser.add_argument('--config', metavar='config', default=False, help="Custom JSON config file location (optional)") + parser = argparse.ArgumentParser( + formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=48) + ) + parser.add_argument( + "--local-only", + action="store_true", + dest="local_only", + help="Don't use Tor (only for development)", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + dest="verbose", + help="Log OnionShare errors to stdout, and web errors to disk", + ) + parser.add_argument( + "--filenames", + metavar="filenames", + nargs="+", + help="List of files or folders to share", + ) + parser.add_argument( + "--config", + metavar="config", + default=False, + help="Custom JSON config file location (optional)", + ) args = parser.parse_args() filenames = args.filenames @@ -118,10 +145,12 @@ def main(): def shutdown(): onion.cleanup() app.cleanup() + qtapp.aboutToQuit.connect(shutdown) # All done sys.exit(qtapp.exec_()) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/onionshare_gui/mode/__init__.py b/onionshare_gui/mode/__init__.py index 8f5ff32b..04709dc2 100644 --- a/onionshare_gui/mode/__init__.py +++ b/onionshare_gui/mode/__init__.py @@ -22,15 +22,19 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings from onionshare.common import AutoStopTimer +from .history import IndividualFileHistoryItem + from ..server_status import ServerStatus from ..threads import OnionThread -from ..threads import AutoStartTimer +from ..threads import AutoStartTimer from ..widgets import Alert + class Mode(QtWidgets.QWidget): """ - The class that ShareMode and ReceiveMode inherit from. + The class that all modes inherit from """ + start_server_finished = QtCore.pyqtSignal() stop_server_finished = QtCore.pyqtSignal() starting_server_step2 = QtCore.pyqtSignal() @@ -39,7 +43,17 @@ class Mode(QtWidgets.QWidget): starting_server_early = QtCore.pyqtSignal() set_server_active = QtCore.pyqtSignal(bool) - def __init__(self, common, qtapp, app, status_bar, server_status_label, system_tray, filenames=None, local_only=False): + def __init__( + self, + common, + qtapp, + app, + status_bar, + server_status_label, + system_tray, + filenames=None, + local_only=False, + ): super(Mode, self).__init__() self.common = common self.qtapp = qtapp @@ -63,7 +77,9 @@ class Mode(QtWidgets.QWidget): self.startup_thread = None # Server status - self.server_status = ServerStatus(self.common, self.qtapp, self.app, None, self.local_only) + self.server_status = ServerStatus( + self.common, self.qtapp, self.app, None, self.local_only + ) 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) @@ -96,16 +112,26 @@ class Mode(QtWidgets.QWidget): """ Returns a human-friendly time delta from given seconds. """ - days = secs//86400 - hours = (secs - days*86400)//3600 - minutes = (secs - days*86400 - hours*3600)//60 - seconds = secs - days*86400 - hours*3600 - minutes*60 + days = secs // 86400 + hours = (secs - days * 86400) // 3600 + minutes = (secs - days * 86400 - hours * 3600) // 60 + seconds = secs - days * 86400 - hours * 3600 - minutes * 60 if not seconds: - seconds = '0' - result = ("{0}{1}, ".format(days, strings._('days_first_letter')) if days else "") + \ - ("{0}{1}, ".format(hours, strings._('hours_first_letter')) if hours else "") + \ - ("{0}{1}, ".format(minutes, strings._('minutes_first_letter')) if minutes else "") + \ - "{0}{1}".format(seconds, strings._('seconds_first_letter')) + seconds = "0" + result = ( + ("{0}{1}, ".format(days, strings._("days_first_letter")) if days else "") + + ( + "{0}{1}, ".format(hours, strings._("hours_first_letter")) + if hours + else "" + ) + + ( + "{0}{1}, ".format(minutes, strings._("minutes_first_letter")) + if minutes + else "" + ) + + "{0}{1}".format(seconds, strings._("seconds_first_letter")) + ) return result @@ -118,25 +144,45 @@ class Mode(QtWidgets.QWidget): if self.server_status.autostart_timer_datetime: now = QtCore.QDateTime.currentDateTime() if self.server_status.local_only: - seconds_remaining = now.secsTo(self.server_status.autostart_timer_widget.dateTime()) + seconds_remaining = now.secsTo( + self.server_status.autostart_timer_widget.dateTime() + ) else: - seconds_remaining = now.secsTo(self.server_status.autostart_timer_datetime.replace(second=0, microsecond=0)) + seconds_remaining = now.secsTo( + self.server_status.autostart_timer_datetime.replace( + second=0, microsecond=0 + ) + ) # Update the server button if seconds_remaining > 0: - self.server_status.server_button.setText(strings._('gui_waiting_to_start').format(self.human_friendly_time(seconds_remaining))) + self.server_status.server_button.setText( + strings._("gui_waiting_to_start").format( + self.human_friendly_time(seconds_remaining) + ) + ) else: - self.server_status.server_button.setText(strings._('gui_please_wait')) + self.server_status.server_button.setText( + strings._("gui_please_wait") + ) # If the auto-stop timer has stopped, stop the server if self.server_status.status == ServerStatus.STATUS_STARTED: - if self.app.autostop_timer_thread and self.common.settings.get('autostop_timer'): + if self.app.autostop_timer_thread and self.common.settings.get( + "autostop_timer" + ): if self.autostop_timer_datetime_delta > 0: now = QtCore.QDateTime.currentDateTime() - seconds_remaining = now.secsTo(self.server_status.autostop_timer_datetime) + seconds_remaining = now.secsTo( + self.server_status.autostop_timer_datetime + ) # Update the server button server_button_text = self.get_stop_server_autostop_timer_text() - self.server_status.server_button.setText(server_button_text.format(self.human_friendly_time(seconds_remaining))) + self.server_status.server_button.setText( + server_button_text.format( + self.human_friendly_time(seconds_remaining) + ) + ) self.status_bar.clearMessage() if not self.app.autostop_timer_thread.is_alive(): @@ -166,22 +212,22 @@ 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('Mode', 'start_server') + 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')) + self.app.set_stealth(self.common.settings.get("use_stealth")) # Clear the status bar self.status_bar.clearMessage() - self.server_status_label.setText('') + self.server_status_label.setText("") # Ensure we always get a new random port each time we might launch an OnionThread self.app.port = None # Start the onion thread. If this share was scheduled for a future date, - # the OnionThread will start and exit 'early' to obtain the port, slug + # the OnionThread will start and exit 'early' to obtain the port, password # and onion address, but it will not start the WebThread yet. if self.server_status.autostart_timer_datetime: self.start_onion_thread(obtain_onion_early=True) @@ -190,7 +236,7 @@ class Mode(QtWidgets.QWidget): # If scheduling a share, delay starting the real share if self.server_status.autostart_timer_datetime: - self.common.log('Mode', 'start_server', 'Starting auto-start timer') + self.common.log("Mode", "start_server", "Starting auto-start timer") self.startup_thread = AutoStartTimer(self) # Once the timer has finished, start the real share, with a WebThread self.startup_thread.success.connect(self.start_scheduled_service) @@ -199,7 +245,7 @@ class Mode(QtWidgets.QWidget): self.startup_thread.start() def start_onion_thread(self, obtain_onion_early=False): - self.common.log('Mode', 'start_server', 'Starting an onion thread') + self.common.log("Mode", "start_server", "Starting an onion thread") self.obtain_onion_early = obtain_onion_early self.onion_thread = OnionThread(self) self.onion_thread.success.connect(self.starting_server_step2.emit) @@ -211,7 +257,7 @@ class Mode(QtWidgets.QWidget): # We start a new OnionThread with the saved scheduled key from settings self.common.settings.load() self.obtain_onion_early = obtain_onion_early - self.common.log('Mode', 'start_server', 'Starting a scheduled onion thread') + self.common.log("Mode", "start_server", "Starting a scheduled onion thread") self.onion_thread = OnionThread(self) self.onion_thread.success.connect(self.starting_server_step2.emit) self.onion_thread.error.connect(self.starting_server_error.emit) @@ -235,7 +281,7 @@ class Mode(QtWidgets.QWidget): """ Step 2 in starting the onionshare server. """ - self.common.log('Mode', 'start_server_step2') + self.common.log("Mode", "start_server_step2") self.start_server_step2_custom() @@ -255,22 +301,28 @@ class Mode(QtWidgets.QWidget): """ Step 3 in starting the onionshare server. """ - self.common.log('Mode', 'start_server_step3') + self.common.log("Mode", "start_server_step3") self.start_server_step3_custom() - if self.common.settings.get('autostop_timer'): + if self.common.settings.get("autostop_timer"): # Convert the date value to seconds between now and then now = QtCore.QDateTime.currentDateTime() - self.autostop_timer_datetime_delta = now.secsTo(self.server_status.autostop_timer_datetime) + self.autostop_timer_datetime_delta = now.secsTo( + self.server_status.autostop_timer_datetime + ) # Start the auto-stop timer if self.autostop_timer_datetime_delta > 0: - self.app.autostop_timer_thread = AutoStopTimer(self.common, self.autostop_timer_datetime_delta) + self.app.autostop_timer_thread = AutoStopTimer( + self.common, self.autostop_timer_datetime_delta + ) self.app.autostop_timer_thread.start() # The auto-stop timer 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_autostop_timer')) + self.start_server_error( + strings._("gui_server_started_after_autostop_timer") + ) def start_server_step3_custom(self): """ @@ -282,7 +334,7 @@ class Mode(QtWidgets.QWidget): """ If there's an error when trying to start the onion service """ - self.common.log('Mode', 'start_server_error') + self.common.log("Mode", "start_server_error") Alert(self.common, error, QtWidgets.QMessageBox.Warning) self.set_server_active.emit(False) @@ -303,16 +355,16 @@ class Mode(QtWidgets.QWidget): """ self.cancel_server_custom() if self.startup_thread: - self.common.log('Mode', 'cancel_server: quitting startup thread') + self.common.log("Mode", "cancel_server: quitting startup thread") self.startup_thread.canceled = True self.app.onion.scheduled_key = None self.app.onion.scheduled_auth_cookie = None self.startup_thread.quit() if self.onion_thread: - self.common.log('Mode', 'cancel_server: quitting onion thread') + self.common.log("Mode", "cancel_server: quitting onion thread") self.onion_thread.quit() if self.web_thread: - self.common.log('Mode', 'cancel_server: quitting web thread') + self.common.log("Mode", "cancel_server: quitting web thread") self.web_thread.quit() self.stop_server() @@ -326,7 +378,7 @@ class Mode(QtWidgets.QWidget): """ Stop the onionshare server. """ - self.common.log('Mode', 'stop_server') + self.common.log("Mode", "stop_server") if self.server_status.status != ServerStatus.STATUS_STOPPED: try: @@ -380,7 +432,9 @@ class Mode(QtWidgets.QWidget): Handle REQUEST_RATE_LIMIT event. """ self.stop_server() - Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) + Alert( + self.common, strings._("error_rate_limit"), QtWidgets.QMessageBox.Critical + ) def handle_request_progress(self, event): """ @@ -417,3 +471,32 @@ class Mode(QtWidgets.QWidget): Handle REQUEST_UPLOAD_CANCELED event. """ pass + + def handle_request_individual_file_started(self, event): + """ + Handle REQUEST_INDVIDIDUAL_FILES_STARTED event. + Used in both Share and Website modes, so implemented here. + """ + self.toggle_history.update_indicator(True) + self.history.requests_count += 1 + self.history.update_requests() + + item = IndividualFileHistoryItem(self.common, event["data"], event["path"]) + self.history.add(event["data"]["id"], item) + + def handle_request_individual_file_progress(self, event): + """ + Handle REQUEST_INDVIDIDUAL_FILES_PROGRESS event. + Used in both Share and Website modes, so implemented here. + """ + self.history.update(event["data"]["id"], event["data"]["bytes"]) + + if self.server_status.status == self.server_status.STATUS_STOPPED: + self.history.cancel(event["data"]["id"]) + + def handle_request_individual_file_canceled(self, event): + """ + Handle REQUEST_INDVIDIDUAL_FILES_CANCELED event. + Used in both Share and Website modes, so implemented here. + """ + self.history.cancel(event["data"]["id"]) diff --git a/onionshare_gui/mode/share_mode/file_selection.py b/onionshare_gui/mode/file_selection.py index 0d4229fe..c505dc03 100644 --- a/onionshare_gui/mode/share_mode/file_selection.py +++ b/onionshare_gui/mode/file_selection.py @@ -22,13 +22,15 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings -from ...widgets import Alert, AddFileDialog +from ..widgets import Alert, AddFileDialog + class DropHereLabel(QtWidgets.QLabel): """ When there are no files or folders in the FileList yet, display the 'drop files here' message and graphic. """ + def __init__(self, common, parent, image=False): self.parent = parent super(DropHereLabel, self).__init__(parent=parent) @@ -39,10 +41,16 @@ class DropHereLabel(QtWidgets.QLabel): self.setAlignment(QtCore.Qt.AlignCenter) if image: - self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png')))) + self.setPixmap( + QtGui.QPixmap.fromImage( + QtGui.QImage( + self.common.get_resource_path("images/logo_transparent.png") + ) + ) + ) else: - self.setText(strings._('gui_drag_and_drop')) - self.setStyleSheet(self.common.css['share_file_selection_drop_here_label']) + self.setText(strings._("gui_drag_and_drop")) + self.setStyleSheet(self.common.css["share_file_selection_drop_here_label"]) self.hide() @@ -57,6 +65,7 @@ class DropCountLabel(QtWidgets.QLabel): While dragging files over the FileList, this counter displays the number of files you're dragging. """ + def __init__(self, common, parent): self.parent = parent super(DropCountLabel, self).__init__(parent=parent) @@ -65,8 +74,8 @@ class DropCountLabel(QtWidgets.QLabel): self.setAcceptDrops(True) self.setAlignment(QtCore.Qt.AlignCenter) - self.setText(strings._('gui_drag_and_drop')) - self.setStyleSheet(self.common.css['share_file_selection_drop_count_label']) + self.setText(strings._("gui_drag_and_drop")) + self.setStyleSheet(self.common.css["share_file_selection_drop_count_label"]) self.hide() def dragEnterEvent(self, event): @@ -78,6 +87,7 @@ class FileList(QtWidgets.QListWidget): """ The list of files and folders in the GUI. """ + files_dropped = QtCore.pyqtSignal() files_updated = QtCore.pyqtSignal() @@ -139,7 +149,7 @@ class FileList(QtWidgets.QListWidget): if self.count() > 0: # Add and delete an empty item, to force all items to get redrawn # This is ugly, but the only way I could figure out how to proceed - item = QtWidgets.QListWidgetItem('fake item') + item = QtWidgets.QListWidgetItem("fake item") self.addItem(item) self.takeItem(self.row(item)) self.update() @@ -149,21 +159,27 @@ class FileList(QtWidgets.QListWidget): # and extend based on the overall width minus that amount. for index in range(self.count()): metrics = QtGui.QFontMetrics(self.item(index).font()) - elided = metrics.elidedText(self.item(index).basename, QtCore.Qt.ElideRight, self.width() - 200) + elided = metrics.elidedText( + self.item(index).basename, QtCore.Qt.ElideRight, self.width() - 200 + ) self.item(index).setText(elided) - def dragEnterEvent(self, event): """ dragEnterEvent for dragging files and directories into the widget. """ if event.mimeData().hasUrls: - self.setStyleSheet(self.common.css['share_file_list_drag_enter']) + self.setStyleSheet(self.common.css["share_file_list_drag_enter"]) count = len(event.mimeData().urls()) - self.drop_count.setText('+{}'.format(count)) + self.drop_count.setText("+{}".format(count)) size_hint = self.drop_count.sizeHint() - self.drop_count.setGeometry(self.width() - size_hint.width() - 10, self.height() - size_hint.height() - 10, size_hint.width(), size_hint.height()) + self.drop_count.setGeometry( + self.width() - size_hint.width() - 10, + self.height() - size_hint.height() - 10, + size_hint.width(), + size_hint.height(), + ) self.drop_count.show() event.accept() else: @@ -173,7 +189,7 @@ class FileList(QtWidgets.QListWidget): """ dragLeaveEvent for dragging files and directories into the widget. """ - self.setStyleSheet(self.common.css['share_file_list_drag_leave']) + self.setStyleSheet(self.common.css["share_file_list_drag_leave"]) self.drop_count.hide() event.accept() self.update() @@ -201,7 +217,7 @@ class FileList(QtWidgets.QListWidget): else: event.ignore() - self.setStyleSheet(self.common.css['share_file_list_drag_leave']) + self.setStyleSheet(self.common.css["share_file_list_drag_leave"]) self.drop_count.hide() self.files_dropped.emit() @@ -238,12 +254,14 @@ class FileList(QtWidgets.QListWidget): # Item's filename attribute and size labels item.filename = filename item_size = QtWidgets.QLabel(size_readable) - item_size.setStyleSheet(self.common.css['share_file_list_item_size']) + item_size.setStyleSheet(self.common.css["share_file_list_item_size"]) - item.basename = os.path.basename(filename.rstrip('/')) + item.basename = os.path.basename(filename.rstrip("/")) # Use the basename as the method with which to sort the list metrics = QtGui.QFontMetrics(item.font()) - elided = metrics.elidedText(item.basename, QtCore.Qt.ElideRight, self.sizeHint().width()) + elided = metrics.elidedText( + item.basename, QtCore.Qt.ElideRight, self.sizeHint().width() + ) item.setData(QtCore.Qt.DisplayRole, elided) # Item's delete button @@ -255,9 +273,13 @@ class FileList(QtWidgets.QListWidget): item.item_button = QtWidgets.QPushButton() item.item_button.setDefault(False) item.item_button.setFlat(True) - item.item_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/file_delete.png')) ) + item.item_button.setIcon( + QtGui.QIcon(self.common.get_resource_path("images/file_delete.png")) + ) item.item_button.clicked.connect(delete_item) - item.item_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + item.item_button.setSizePolicy( + QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed + ) # Item info widget, with a white background item_info_layout = QtWidgets.QHBoxLayout() @@ -265,7 +287,7 @@ class FileList(QtWidgets.QListWidget): item_info_layout.addWidget(item_size) item_info_layout.addWidget(item.item_button) item_info = QtWidgets.QWidget() - item_info.setObjectName('item-info') + item_info.setObjectName("item-info") item_info.setLayout(item_info_layout) # Create the item's widget and layouts @@ -288,6 +310,7 @@ class FileSelection(QtWidgets.QVBoxLayout): The list of files and folders in the GUI, as well as buttons to add and delete the files and folders. """ + def __init__(self, common, parent): super(FileSelection, self).__init__() @@ -303,21 +326,21 @@ class FileSelection(QtWidgets.QVBoxLayout): self.file_list.files_updated.connect(self.update) # Buttons - if self.common.platform == 'Darwin': + if self.common.platform == "Darwin": # The macOS sandbox makes it so the Mac version needs separate add files # and folders buttons, in order to use native file selection dialogs - self.add_files_button = QtWidgets.QPushButton(strings._('gui_add_files')) + self.add_files_button = QtWidgets.QPushButton(strings._("gui_add_files")) self.add_files_button.clicked.connect(self.add_files) - self.add_folder_button = QtWidgets.QPushButton(strings._('gui_add_folder')) + self.add_folder_button = QtWidgets.QPushButton(strings._("gui_add_folder")) self.add_folder_button.clicked.connect(self.add_folder) else: - self.add_button = QtWidgets.QPushButton(strings._('gui_add')) + self.add_button = QtWidgets.QPushButton(strings._("gui_add")) self.add_button.clicked.connect(self.add) - self.delete_button = QtWidgets.QPushButton(strings._('gui_delete')) + self.delete_button = QtWidgets.QPushButton(strings._("gui_delete")) self.delete_button.clicked.connect(self.delete) button_layout = QtWidgets.QHBoxLayout() button_layout.addStretch() - if self.common.platform == 'Darwin': + if self.common.platform == "Darwin": button_layout.addWidget(self.add_files_button) button_layout.addWidget(self.add_folder_button) else: @@ -336,14 +359,14 @@ class FileSelection(QtWidgets.QVBoxLayout): """ # All buttons should be hidden if the server is on if self.server_on: - if self.common.platform == 'Darwin': + if self.common.platform == "Darwin": self.add_files_button.hide() self.add_folder_button.hide() else: self.add_button.hide() self.delete_button.hide() else: - if self.common.platform == 'Darwin': + if self.common.platform == "Darwin": self.add_files_button.show() self.add_folder_button.show() else: @@ -362,7 +385,7 @@ class FileSelection(QtWidgets.QVBoxLayout): """ Add button clicked. """ - file_dialog = AddFileDialog(self.common, caption=strings._('gui_choose_items')) + file_dialog = AddFileDialog(self.common, caption=strings._("gui_choose_items")) if file_dialog.exec_() == QtWidgets.QDialog.Accepted: for filename in file_dialog.selectedFiles(): self.file_list.add_file(filename) @@ -374,7 +397,9 @@ class FileSelection(QtWidgets.QVBoxLayout): """ Add files button clicked. """ - files = QtWidgets.QFileDialog.getOpenFileNames(self.parent, caption=strings._('gui_choose_items')) + files = QtWidgets.QFileDialog.getOpenFileNames( + self.parent, caption=strings._("gui_choose_items") + ) filenames = files[0] for filename in filenames: self.file_list.add_file(filename) @@ -383,9 +408,11 @@ class FileSelection(QtWidgets.QVBoxLayout): """ Add folder button clicked. """ - filename = QtWidgets.QFileDialog.getExistingDirectory(self.parent, - caption=strings._('gui_choose_items'), - options=QtWidgets.QFileDialog.ShowDirsOnly) + filename = QtWidgets.QFileDialog.getExistingDirectory( + self.parent, + caption=strings._("gui_choose_items"), + options=QtWidgets.QFileDialog.ShowDirsOnly, + ) self.file_list.add_file(filename) def delete(self): diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 1546cb68..85eec7e4 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -31,6 +31,7 @@ class HistoryItem(QtWidgets.QWidget): """ The base history item """ + STATUS_STARTED = 0 STATUS_FINISHED = 1 STATUS_CANCELED = 2 @@ -49,34 +50,42 @@ class HistoryItem(QtWidgets.QWidget): When an item finishes, returns a string displaying the start/end datetime range. started is a datetime object. """ - return self._get_label_text('gui_all_modes_transfer_finished', 'gui_all_modes_transfer_finished_range', started) + return self._get_label_text( + "gui_all_modes_transfer_finished", + "gui_all_modes_transfer_finished_range", + started, + ) def get_canceled_label_text(self, started): """ When an item is canceled, returns a string displaying the start/end datetime range. started is a datetime object. """ - return self._get_label_text('gui_all_modes_transfer_canceled', 'gui_all_modes_transfer_canceled_range', started) + return self._get_label_text( + "gui_all_modes_transfer_canceled", + "gui_all_modes_transfer_canceled_range", + started, + ) def _get_label_text(self, string_name, string_range_name, started): """ Return a string that contains a date, or date range. """ ended = datetime.now() - if started.year == ended.year and started.month == ended.month and started.day == ended.day: + if ( + started.year == ended.year + and started.month == ended.month + and started.day == ended.day + ): if started.hour == ended.hour and started.minute == ended.minute: - text = strings._(string_name).format( - started.strftime("%b %d, %I:%M%p") - ) + text = strings._(string_name).format(started.strftime("%b %d, %I:%M%p")) else: text = strings._(string_range_name).format( - started.strftime("%b %d, %I:%M%p"), - ended.strftime("%I:%M%p") + started.strftime("%b %d, %I:%M%p"), ended.strftime("%I:%M%p") ) else: text = strings._(string_range_name).format( - started.strftime("%b %d, %I:%M%p"), - ended.strftime("%b %d, %I:%M%p") + started.strftime("%b %d, %I:%M%p"), ended.strftime("%b %d, %I:%M%p") ) return text @@ -85,6 +94,7 @@ class ShareHistoryItem(HistoryItem): """ Download history item, for share mode """ + def __init__(self, common, id, total_bytes): super(ShareHistoryItem, self).__init__() self.common = common @@ -97,7 +107,11 @@ class ShareHistoryItem(HistoryItem): self.status = HistoryItem.STATUS_STARTED # Label - self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started_dt.strftime("%b %d, %I:%M%p"))) + self.label = QtWidgets.QLabel( + strings._("gui_all_modes_transfer_started").format( + self.started_dt.strftime("%b %d, %I:%M%p") + ) + ) # Progress bar self.progress_bar = QtWidgets.QProgressBar() @@ -107,7 +121,9 @@ class ShareHistoryItem(HistoryItem): self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(total_bytes) self.progress_bar.setValue(0) - self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) + self.progress_bar.setStyleSheet( + self.common.css["downloads_uploads_progress_bar"] + ) self.progress_bar.total_bytes = total_bytes # Layout @@ -124,8 +140,9 @@ class ShareHistoryItem(HistoryItem): self.progress_bar.setValue(downloaded_bytes) if downloaded_bytes == self.progress_bar.total_bytes: - pb_fmt = strings._('gui_all_modes_progress_complete').format( - self.common.format_seconds(time.time() - self.started)) + pb_fmt = strings._("gui_all_modes_progress_complete").format( + self.common.format_seconds(time.time() - self.started) + ) # Change the label self.label.setText(self.get_finished_label_text(self.started_dt)) @@ -137,24 +154,26 @@ class ShareHistoryItem(HistoryItem): # Wait a couple of seconds for the download rate to stabilize. # This prevents a "Windows copy dialog"-esque experience at # the beginning of the download. - pb_fmt = strings._('gui_all_modes_progress_starting').format( - self.common.human_readable_filesize(downloaded_bytes)) + pb_fmt = strings._("gui_all_modes_progress_starting").format( + self.common.human_readable_filesize(downloaded_bytes) + ) else: - pb_fmt = strings._('gui_all_modes_progress_eta').format( + pb_fmt = strings._("gui_all_modes_progress_eta").format( self.common.human_readable_filesize(downloaded_bytes), - self.estimated_time_remaining) + self.estimated_time_remaining, + ) self.progress_bar.setFormat(pb_fmt) def cancel(self): - self.progress_bar.setFormat(strings._('gui_canceled')) + self.progress_bar.setFormat(strings._("gui_canceled")) self.status = HistoryItem.STATUS_CANCELED @property def estimated_time_remaining(self): - return self.common.estimated_time_remaining(self.downloaded_bytes, - self.total_bytes, - self.started) + return self.common.estimated_time_remaining( + self.downloaded_bytes, self.total_bytes, self.started + ) class ReceiveHistoryItemFile(QtWidgets.QWidget): @@ -162,7 +181,9 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget): super(ReceiveHistoryItemFile, self).__init__() self.common = common - self.common.log('ReceiveHistoryItemFile', '__init__', 'filename: {}'.format(filename)) + self.common.log( + "ReceiveHistoryItemFile", "__init__", "filename: {}".format(filename) + ) self.filename = filename self.dir = None @@ -174,11 +195,13 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget): # File size label self.filesize_label = QtWidgets.QLabel() - self.filesize_label.setStyleSheet(self.common.css['receive_file_size']) + self.filesize_label.setStyleSheet(self.common.css["receive_file_size"]) self.filesize_label.hide() # Folder button - folder_pixmap = QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/open_folder.png'))) + folder_pixmap = QtGui.QPixmap.fromImage( + QtGui.QImage(self.common.get_resource_path("images/open_folder.png")) + ) folder_icon = QtGui.QIcon(folder_pixmap) self.folder_button = QtWidgets.QPushButton() self.folder_button.clicked.connect(self.open_folder) @@ -213,29 +236,37 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget): """ Open the downloads folder, with the file selected, in a cross-platform manner """ - self.common.log('ReceiveHistoryItemFile', 'open_folder') + self.common.log("ReceiveHistoryItemFile", "open_folder") if not self.dir: - self.common.log('ReceiveHistoryItemFile', 'open_folder', "dir has not been set yet, can't open folder") + self.common.log( + "ReceiveHistoryItemFile", + "open_folder", + "dir has not been set yet, can't open folder", + ) return abs_filename = os.path.join(self.dir, self.filename) # Linux - if self.common.platform == 'Linux' or self.common.platform == 'BSD': + if self.common.platform == "Linux" or self.common.platform == "BSD": try: # If nautilus is available, open it - subprocess.Popen(['nautilus', abs_filename]) + subprocess.Popen(["nautilus", abs_filename]) except: - Alert(self.common, strings._('gui_open_folder_error_nautilus').format(abs_filename)) + Alert( + self.common, + strings._("gui_open_folder_error_nautilus").format(abs_filename), + ) # macOS - elif self.common.platform == 'Darwin': - subprocess.call(['open', '-R', abs_filename]) + elif self.common.platform == "Darwin": + subprocess.call(["open", "-R", abs_filename]) # Windows - elif self.common.platform == 'Windows': - subprocess.Popen(['explorer', '/select,{}'.format(abs_filename)]) + elif self.common.platform == "Windows": + subprocess.Popen(["explorer", "/select,{}".format(abs_filename)]) + class ReceiveHistoryItem(HistoryItem): def __init__(self, common, id, content_length): @@ -247,7 +278,11 @@ class ReceiveHistoryItem(HistoryItem): self.status = HistoryItem.STATUS_STARTED # Label - self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started.strftime("%b %d, %I:%M%p"))) + self.label = QtWidgets.QLabel( + strings._("gui_all_modes_transfer_started").format( + self.started.strftime("%b %d, %I:%M%p") + ) + ) # Progress bar self.progress_bar = QtWidgets.QProgressBar() @@ -256,13 +291,15 @@ class ReceiveHistoryItem(HistoryItem): self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter) self.progress_bar.setMinimum(0) self.progress_bar.setValue(0) - self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) + self.progress_bar.setStyleSheet( + self.common.css["downloads_uploads_progress_bar"] + ) # This layout contains file widgets self.files_layout = QtWidgets.QVBoxLayout() self.files_layout.setContentsMargins(0, 0, 0, 0) files_widget = QtWidgets.QWidget() - files_widget.setStyleSheet(self.common.css['receive_file']) + files_widget.setStyleSheet(self.common.css["receive_file"]) files_widget.setLayout(self.files_layout) # Layout @@ -281,10 +318,10 @@ class ReceiveHistoryItem(HistoryItem): Using the progress from Web, update the progress bar and file size labels for each file """ - if data['action'] == 'progress': + if data["action"] == "progress": total_uploaded_bytes = 0 - for filename in data['progress']: - total_uploaded_bytes += data['progress'][filename]['uploaded_bytes'] + for filename in data["progress"]: + total_uploaded_bytes += data["progress"][filename]["uploaded_bytes"] # Update the progress bar self.progress_bar.setMaximum(self.content_length) @@ -292,35 +329,39 @@ class ReceiveHistoryItem(HistoryItem): elapsed = datetime.now() - self.started if elapsed.seconds < 10: - pb_fmt = strings._('gui_all_modes_progress_starting').format( - self.common.human_readable_filesize(total_uploaded_bytes)) + pb_fmt = strings._("gui_all_modes_progress_starting").format( + self.common.human_readable_filesize(total_uploaded_bytes) + ) else: estimated_time_remaining = self.common.estimated_time_remaining( - total_uploaded_bytes, - self.content_length, - self.started.timestamp()) - pb_fmt = strings._('gui_all_modes_progress_eta').format( + total_uploaded_bytes, self.content_length, self.started.timestamp() + ) + pb_fmt = strings._("gui_all_modes_progress_eta").format( self.common.human_readable_filesize(total_uploaded_bytes), - estimated_time_remaining) + estimated_time_remaining, + ) # Using list(progress) to avoid "RuntimeError: dictionary changed size during iteration" - for filename in list(data['progress']): + for filename in list(data["progress"]): # Add a new file if needed if filename not in self.files: self.files[filename] = ReceiveHistoryItemFile(self.common, filename) self.files_layout.addWidget(self.files[filename]) # Update the file - self.files[filename].update(data['progress'][filename]['uploaded_bytes'], data['progress'][filename]['complete']) + self.files[filename].update( + data["progress"][filename]["uploaded_bytes"], + data["progress"][filename]["complete"], + ) - elif data['action'] == 'rename': - self.files[data['old_filename']].rename(data['new_filename']) - self.files[data['new_filename']] = self.files.pop(data['old_filename']) + elif data["action"] == "rename": + self.files[data["old_filename"]].rename(data["new_filename"]) + self.files[data["new_filename"]] = self.files.pop(data["old_filename"]) - elif data['action'] == 'set_dir': - self.files[data['filename']].set_dir(data['dir']) + elif data["action"] == "set_dir": + self.files[data["filename"]].set_dir(data["dir"]) - elif data['action'] == 'finished': + elif data["action"] == "finished": # Change the status self.status = HistoryItem.STATUS_FINISHED @@ -330,7 +371,7 @@ class ReceiveHistoryItem(HistoryItem): # Change the label self.label.setText(self.get_finished_label_text(self.started)) - elif data['action'] == 'canceled': + elif data["action"] == "canceled": # Change the status self.status = HistoryItem.STATUS_CANCELED @@ -341,10 +382,128 @@ class ReceiveHistoryItem(HistoryItem): self.label.setText(self.get_canceled_label_text(self.started)) +class IndividualFileHistoryItem(HistoryItem): + """ + Individual file history item, for share mode viewing of individual files + """ + + def __init__(self, common, data, path): + super(IndividualFileHistoryItem, self).__init__() + self.status = HistoryItem.STATUS_STARTED + self.common = common + + self.id = id + self.path = path + self.total_bytes = 0 + self.downloaded_bytes = 0 + self.started = time.time() + self.started_dt = datetime.fromtimestamp(self.started) + self.status = HistoryItem.STATUS_STARTED + + self.directory_listing = "directory_listing" in data + + # Labels + self.timestamp_label = QtWidgets.QLabel( + self.started_dt.strftime("%b %d, %I:%M%p") + ) + self.timestamp_label.setStyleSheet( + self.common.css["history_individual_file_timestamp_label"] + ) + self.path_label = QtWidgets.QLabel("{}".format(self.path)) + self.status_code_label = QtWidgets.QLabel() + + # Progress bar + self.progress_bar = QtWidgets.QProgressBar() + self.progress_bar.setTextVisible(True) + self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter) + self.progress_bar.setValue(0) + self.progress_bar.setStyleSheet( + self.common.css["downloads_uploads_progress_bar"] + ) + + # Text layout + labels_layout = QtWidgets.QHBoxLayout() + labels_layout.addWidget(self.timestamp_label) + labels_layout.addWidget(self.path_label) + labels_layout.addWidget(self.status_code_label) + labels_layout.addStretch() + + # Layout + layout = QtWidgets.QVBoxLayout() + layout.addLayout(labels_layout) + layout.addWidget(self.progress_bar) + self.setLayout(layout) + + # Is a status code already sent? + if "status_code" in data: + self.status_code_label.setText("{}".format(data["status_code"])) + if data["status_code"] >= 200 and data["status_code"] < 300: + self.status_code_label.setStyleSheet( + self.common.css["history_individual_file_status_code_label_2xx"] + ) + if data["status_code"] >= 400 and data["status_code"] < 500: + self.status_code_label.setStyleSheet( + self.common.css["history_individual_file_status_code_label_4xx"] + ) + self.status = HistoryItem.STATUS_FINISHED + self.progress_bar.hide() + return + + else: + self.total_bytes = data["filesize"] + self.progress_bar.setMinimum(0) + self.progress_bar.setMaximum(data["filesize"]) + self.progress_bar.total_bytes = data["filesize"] + + # Start at 0 + self.update(0) + + def update(self, downloaded_bytes): + self.downloaded_bytes = downloaded_bytes + + self.progress_bar.setValue(downloaded_bytes) + if downloaded_bytes == self.progress_bar.total_bytes: + self.status_code_label.setText("200") + self.status_code_label.setStyleSheet( + self.common.css["history_individual_file_status_code_label_2xx"] + ) + self.progress_bar.hide() + self.status = HistoryItem.STATUS_FINISHED + + else: + elapsed = time.time() - self.started + if elapsed < 10: + # Wait a couple of seconds for the download rate to stabilize. + # This prevents a "Windows copy dialog"-esque experience at + # the beginning of the download. + pb_fmt = strings._("gui_all_modes_progress_starting").format( + self.common.human_readable_filesize(downloaded_bytes) + ) + else: + pb_fmt = strings._("gui_all_modes_progress_eta").format( + self.common.human_readable_filesize(downloaded_bytes), + self.estimated_time_remaining, + ) + + self.progress_bar.setFormat(pb_fmt) + + def cancel(self): + self.progress_bar.setFormat(strings._("gui_canceled")) + self.status = HistoryItem.STATUS_CANCELED + + @property + def estimated_time_remaining(self): + return self.common.estimated_time_remaining( + self.downloaded_bytes, self.total_bytes, self.started + ) + + class HistoryItemList(QtWidgets.QScrollArea): """ List of items """ + def __init__(self, common): super(HistoryItemList, self).__init__() self.common = common @@ -404,45 +563,53 @@ class HistoryItemList(QtWidgets.QScrollArea): Reset all items, emptying the list. Override this method. """ for key, item in self.items.copy().items(): - if item.status != HistoryItem.STATUS_STARTED: - self.items_layout.removeWidget(item) - item.close() - del self.items[key] + self.items_layout.removeWidget(item) + item.close() + del self.items[key] + class History(QtWidgets.QWidget): """ A history of what's happened so far in this mode. This contains an internal object full of a scrollable list of items. """ - def __init__(self, common, empty_image, empty_text, header_text): + + def __init__(self, common, empty_image, empty_text, header_text, mode=""): super(History, self).__init__() self.common = common + self.mode = mode self.setMinimumWidth(350) # In progress and completed counters self.in_progress_count = 0 self.completed_count = 0 + self.requests_count = 0 - # In progress and completed labels + # In progress, completed, and requests labels self.in_progress_label = QtWidgets.QLabel() - self.in_progress_label.setStyleSheet(self.common.css['mode_info_label']) + self.in_progress_label.setStyleSheet(self.common.css["mode_info_label"]) self.completed_label = QtWidgets.QLabel() - self.completed_label.setStyleSheet(self.common.css['mode_info_label']) + self.completed_label.setStyleSheet(self.common.css["mode_info_label"]) + self.requests_label = QtWidgets.QLabel() + self.requests_label.setStyleSheet(self.common.css["mode_info_label"]) # Header self.header_label = QtWidgets.QLabel(header_text) - self.header_label.setStyleSheet(self.common.css['downloads_uploads_label']) - clear_button = QtWidgets.QPushButton(strings._('gui_all_modes_clear_history')) - clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) - clear_button.setFlat(True) - clear_button.clicked.connect(self.reset) + self.header_label.setStyleSheet(self.common.css["downloads_uploads_label"]) + self.clear_button = QtWidgets.QPushButton( + strings._("gui_all_modes_clear_history") + ) + self.clear_button.setStyleSheet(self.common.css["downloads_uploads_clear"]) + self.clear_button.setFlat(True) + self.clear_button.clicked.connect(self.reset) header_layout = QtWidgets.QHBoxLayout() header_layout.addWidget(self.header_label) header_layout.addStretch() header_layout.addWidget(self.in_progress_label) header_layout.addWidget(self.completed_label) - header_layout.addWidget(clear_button) + header_layout.addWidget(self.requests_label) + header_layout.addWidget(self.clear_button) # When there are no items self.empty_image = QtWidgets.QLabel() @@ -450,14 +617,14 @@ class History(QtWidgets.QWidget): self.empty_image.setPixmap(empty_image) self.empty_text = QtWidgets.QLabel(empty_text) self.empty_text.setAlignment(QtCore.Qt.AlignCenter) - self.empty_text.setStyleSheet(self.common.css['downloads_uploads_empty_text']) + self.empty_text.setStyleSheet(self.common.css["downloads_uploads_empty_text"]) empty_layout = QtWidgets.QVBoxLayout() empty_layout.addStretch() empty_layout.addWidget(self.empty_image) empty_layout.addWidget(self.empty_text) empty_layout.addStretch() self.empty = QtWidgets.QWidget() - self.empty.setStyleSheet(self.common.css['downloads_uploads_empty']) + self.empty.setStyleSheet(self.common.css["downloads_uploads_empty"]) self.empty.setLayout(empty_layout) # When there are items @@ -482,7 +649,7 @@ class History(QtWidgets.QWidget): """ Add a new item. """ - self.common.log('History', 'add', 'id: {}, item: {}'.format(id, item)) + self.common.log("History", "add", "id: {}, item: {}".format(id, item)) # Hide empty, show not empty self.empty.hide() @@ -520,27 +687,56 @@ class History(QtWidgets.QWidget): self.completed_count = 0 self.update_completed() + # Reset web requests counter + self.requests_count = 0 + self.update_requests() + def update_completed(self): """ Update the 'completed' widget. """ if self.completed_count == 0: - image = self.common.get_resource_path('images/share_completed_none.png') + image = self.common.get_resource_path("images/history_completed_none.png") else: - image = self.common.get_resource_path('images/share_completed.png') - self.completed_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.completed_count)) - self.completed_label.setToolTip(strings._('history_completed_tooltip').format(self.completed_count)) + image = self.common.get_resource_path("images/history_completed.png") + self.completed_label.setText( + '<img src="{0:s}" /> {1:d}'.format(image, self.completed_count) + ) + self.completed_label.setToolTip( + strings._("history_completed_tooltip").format(self.completed_count) + ) def update_in_progress(self): """ Update the 'in progress' widget. """ if self.in_progress_count == 0: - image = self.common.get_resource_path('images/share_in_progress_none.png') + image = self.common.get_resource_path("images/history_in_progress_none.png") else: - image = self.common.get_resource_path('images/share_in_progress.png') - self.in_progress_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.in_progress_count)) - self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip').format(self.in_progress_count)) + image = self.common.get_resource_path("images/history_in_progress.png") + + self.in_progress_label.setText( + '<img src="{0:s}" /> {1:d}'.format(image, self.in_progress_count) + ) + self.in_progress_label.setToolTip( + strings._("history_in_progress_tooltip").format(self.in_progress_count) + ) + + def update_requests(self): + """ + Update the 'web requests' widget. + """ + if self.requests_count == 0: + image = self.common.get_resource_path("images/history_requests_none.png") + else: + image = self.common.get_resource_path("images/history_requests.png") + + self.requests_label.setText( + '<img src="{0:s}" /> {1:d}'.format(image, self.requests_count) + ) + self.requests_label.setToolTip( + strings._("history_requests_tooltip").format(self.requests_count) + ) class ToggleHistory(QtWidgets.QPushButton): @@ -548,6 +744,7 @@ class ToggleHistory(QtWidgets.QPushButton): Widget for toggling showing or hiding the history, as well as keeping track of the indicator counter if it's hidden """ + def __init__(self, common, current_mode, history_widget, icon, selected_icon): super(ToggleHistory, self).__init__() self.common = common @@ -567,13 +764,15 @@ class ToggleHistory(QtWidgets.QPushButton): # Keep track of indicator self.indicator_count = 0 self.indicator_label = QtWidgets.QLabel(parent=self) - self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) + self.indicator_label.setStyleSheet( + self.common.css["download_uploads_indicator"] + ) self.update_indicator() def update_indicator(self, increment=False): """ Update the display of the indicator count. If increment is True, then - only increment the counter if Downloads is hidden. + only increment the counter if History is hidden. """ if increment and not self.history_widget.isVisible(): self.indicator_count += 1 @@ -584,14 +783,16 @@ class ToggleHistory(QtWidgets.QPushButton): self.indicator_label.hide() else: size = self.indicator_label.sizeHint() - self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height()) + self.indicator_label.setGeometry( + 35 - size.width(), 0, size.width(), size.height() + ) self.indicator_label.show() def toggle_clicked(self): """ Toggle showing and hiding the history widget """ - self.common.log('ToggleHistory', 'toggle_clicked') + self.common.log("ToggleHistory", "toggle_clicked") if self.history_widget.isVisible(): self.history_widget.hide() diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index 4c0b49ba..a0507949 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -25,19 +25,21 @@ from onionshare.web import Web from ..history import History, ToggleHistory, ReceiveHistoryItem from .. import Mode + class ReceiveMode(Mode): """ Parts of the main window UI for receiving files. """ + def init(self): """ Custom initialization for ReceiveMode. """ # Create the Web object - self.web = Web(self.common, True, 'receive') + self.web = Web(self.common, True, "receive") # Server status - self.server_status.set_mode('receive') + self.server_status.set_mode("receive") self.server_status.server_started_finished.connect(self.update_primary_action) self.server_status.server_stopped.connect(self.update_primary_action) self.server_status.server_canceled.connect(self.update_primary_action) @@ -49,21 +51,31 @@ class ReceiveMode(Mode): # Upload history self.history = History( self.common, - QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/receive_icon_transparent.png'))), - strings._('gui_receive_mode_no_files'), - strings._('gui_all_modes_history') + QtGui.QPixmap.fromImage( + QtGui.QImage( + self.common.get_resource_path("images/receive_icon_transparent.png") + ) + ), + strings._("gui_receive_mode_no_files"), + strings._("gui_all_modes_history"), ) self.history.hide() # Toggle history self.toggle_history = ToggleHistory( - self.common, self, self.history, - QtGui.QIcon(self.common.get_resource_path('images/receive_icon_toggle.png')), - QtGui.QIcon(self.common.get_resource_path('images/receive_icon_toggle_selected.png')) + self.common, + self, + self.history, + QtGui.QIcon( + self.common.get_resource_path("images/receive_icon_toggle.png") + ), + QtGui.QIcon( + self.common.get_resource_path("images/receive_icon_toggle_selected.png") + ), ) # Receive mode warning - receive_warning = QtWidgets.QLabel(strings._('gui_receive_mode_warning')) + receive_warning = QtWidgets.QLabel(strings._("gui_receive_mode_warning")) receive_warning.setMinimumHeight(80) receive_warning.setWordWrap(True) @@ -90,20 +102,25 @@ class ReceiveMode(Mode): """ Return the string to put on the stop server button, if there's an auto-stop timer """ - return strings._('gui_receive_stop_server_autostop_timer') + return strings._("gui_receive_stop_server_autostop_timer") def autostop_timer_finished_should_stop_server(self): """ The auto-stop timer expired, should we stop the server? Returns a bool """ # If there were no attempts to upload files, or all uploads are done, we can stop - if self.web.receive_mode.upload_count == 0 or not self.web.receive_mode.uploads_in_progress: + if ( + self.web.receive_mode.cur_history_id == 0 + or not self.web.receive_mode.uploads_in_progress + ): self.server_status.stop_server() - self.server_status_label.setText(strings._('close_on_autostop_timer')) + self.server_status_label.setText(strings._("close_on_autostop_timer")) return True # An upload is probably still running - hold off on stopping the share, but block new shares. else: - self.server_status_label.setText(strings._('gui_receive_mode_autostop_timer_waiting')) + self.server_status_label.setText( + strings._("gui_receive_mode_autostop_timer_waiting") + ) self.web.receive_mode.can_upload = False return False @@ -112,8 +129,8 @@ class ReceiveMode(Mode): Starting the server. """ # Reset web counters - self.web.receive_mode.upload_count = 0 - self.web.error404_count = 0 + self.web.receive_mode.cur_history_id = 0 + self.web.reset_invalid_passwords() # Hide and reset the uploads if we have previously shared self.reset_info_counters() @@ -136,56 +153,68 @@ class ReceiveMode(Mode): """ Handle REQUEST_LOAD event. """ - self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_page_loaded_message')) + self.system_tray.showMessage( + strings._("systray_page_loaded_title"), + strings._("systray_page_loaded_message"), + ) def handle_request_started(self, event): """ Handle REQUEST_STARTED event. """ - item = ReceiveHistoryItem(self.common, event["data"]["id"], event["data"]["content_length"]) + item = ReceiveHistoryItem( + self.common, event["data"]["id"], event["data"]["content_length"] + ) self.history.add(event["data"]["id"], item) self.toggle_history.update_indicator(True) self.history.in_progress_count += 1 self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_receive_started_title'), strings._('systray_receive_started_message')) + self.system_tray.showMessage( + strings._("systray_receive_started_title"), + strings._("systray_receive_started_message"), + ) def handle_request_progress(self, event): """ Handle REQUEST_PROGRESS event. """ - self.history.update(event["data"]["id"], { - 'action': 'progress', - 'progress': event["data"]["progress"] - }) + self.history.update( + event["data"]["id"], + {"action": "progress", "progress": event["data"]["progress"]}, + ) def handle_request_upload_file_renamed(self, event): """ Handle REQUEST_UPLOAD_FILE_RENAMED event. """ - self.history.update(event["data"]["id"], { - 'action': 'rename', - 'old_filename': event["data"]["old_filename"], - 'new_filename': event["data"]["new_filename"] - }) + self.history.update( + event["data"]["id"], + { + "action": "rename", + "old_filename": event["data"]["old_filename"], + "new_filename": event["data"]["new_filename"], + }, + ) def handle_request_upload_set_dir(self, event): """ Handle REQUEST_UPLOAD_SET_DIR event. """ - self.history.update(event["data"]["id"], { - 'action': 'set_dir', - 'filename': event["data"]["filename"], - 'dir': event["data"]["dir"] - }) + self.history.update( + event["data"]["id"], + { + "action": "set_dir", + "filename": event["data"]["filename"], + "dir": event["data"]["dir"], + }, + ) def handle_request_upload_finished(self, event): """ Handle REQUEST_UPLOAD_FINISHED event. """ - self.history.update(event["data"]["id"], { - 'action': 'finished' - }) + self.history.update(event["data"]["id"], {"action": "finished"}) self.history.completed_count += 1 self.history.in_progress_count -= 1 self.history.update_completed() @@ -195,9 +224,7 @@ class ReceiveMode(Mode): """ Handle REQUEST_UPLOAD_CANCELED event. """ - self.history.update(event["data"]["id"], { - 'action': 'canceled' - }) + self.history.update(event["data"]["id"], {"action": "canceled"}) self.history.in_progress_count -= 1 self.history.update_in_progress() @@ -212,6 +239,8 @@ class ReceiveMode(Mode): Set the info counters back to zero. """ self.history.reset() + self.toggle_history.indicator_count = 0 + self.toggle_history.update_indicator() def update_primary_action(self): - self.common.log('ReceiveMode', 'update_primary_action') + self.common.log("ReceiveMode", "update_primary_action") diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index 6cb50b2b..d0cc6a04 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -25,7 +25,7 @@ from onionshare.onion import * from onionshare.common import Common from onionshare.web import Web -from .file_selection import FileSelection +from ..file_selection import FileSelection from .threads import CompressThread from .. import Mode from ..history import History, ToggleHistory, ShareHistoryItem @@ -36,6 +36,7 @@ class ShareMode(Mode): """ Parts of the main window UI for sharing files. """ + def init(self): """ Custom initialization for ReceiveMode. @@ -44,7 +45,7 @@ class ShareMode(Mode): self.compress_thread = None # Create the Web object - self.web = Web(self.common, True, 'share') + self.web = Web(self.common, True, "share") # File selection self.file_selection = FileSelection(self.common, self) @@ -53,7 +54,7 @@ class ShareMode(Mode): self.file_selection.file_list.add_file(filename) # Server status - self.server_status.set_mode('share', self.file_selection) + self.server_status.set_mode("share", 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) @@ -68,15 +69,19 @@ class ShareMode(Mode): # Filesize warning self.filesize_warning = QtWidgets.QLabel() self.filesize_warning.setWordWrap(True) - self.filesize_warning.setStyleSheet(self.common.css['share_filesize_warning']) + self.filesize_warning.setStyleSheet(self.common.css["share_filesize_warning"]) self.filesize_warning.hide() # Download history self.history = History( self.common, - QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/share_icon_transparent.png'))), - strings._('gui_share_mode_no_files'), - strings._('gui_all_modes_history') + QtGui.QPixmap.fromImage( + QtGui.QImage( + self.common.get_resource_path("images/share_icon_transparent.png") + ) + ), + strings._("gui_share_mode_no_files"), + strings._("gui_all_modes_history"), ) self.history.hide() @@ -86,9 +91,13 @@ class ShareMode(Mode): # Toggle history self.toggle_history = ToggleHistory( - self.common, self, self.history, - QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle.png')), - QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle_selected.png')) + self.common, + self, + self.history, + QtGui.QIcon(self.common.get_resource_path("images/share_icon_toggle.png")), + QtGui.QIcon( + self.common.get_resource_path("images/share_icon_toggle_selected.png") + ), ) # Top bar @@ -125,20 +134,22 @@ class ShareMode(Mode): """ Return the string to put on the stop server button, if there's an auto-stop timer """ - return strings._('gui_share_stop_server_autostop_timer') + return strings._("gui_share_stop_server_autostop_timer") def autostop_timer_finished_should_stop_server(self): """ The auto-stop timer expired, should we stop the server? Returns a bool """ # If there were no attempts to download the share, or all downloads are done, we can stop - if self.web.share_mode.download_count == 0 or self.web.done: + if self.web.share_mode.cur_history_id == 0 or self.web.done: self.server_status.stop_server() - self.server_status_label.setText(strings._('close_on_autostop_timer')) + self.server_status_label.setText(strings._("close_on_autostop_timer")) return True # A download is probably still running - hold off on stopping the share else: - self.server_status_label.setText(strings._('gui_share_mode_autostop_timer_waiting')) + self.server_status_label.setText( + strings._("gui_share_mode_autostop_timer_waiting") + ) return False def start_server_custom(self): @@ -146,8 +157,8 @@ class ShareMode(Mode): Starting the server. """ # Reset web counters - self.web.share_mode.download_count = 0 - self.web.error404_count = 0 + self.web.share_mode.cur_history_id = 0 + self.web.reset_invalid_passwords() # Hide and reset the downloads if we have previously shared self.reset_info_counters() @@ -162,7 +173,9 @@ class ShareMode(Mode): 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._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 @@ -216,7 +229,7 @@ class ShareMode(Mode): Stop the compression thread on cancel """ if self.compress_thread: - self.common.log('ShareMode', 'cancel_server: quitting compress thread') + self.common.log("ShareMode", "cancel_server: quitting compress thread") self.compress_thread.quit() def handle_tor_broke_custom(self): @@ -225,12 +238,6 @@ class ShareMode(Mode): """ self.primary_action.hide() - def handle_request_load(self, event): - """ - Handle REQUEST_LOAD event. - """ - self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_page_loaded_message')) - def handle_request_started(self, event): """ Handle REQUEST_STARTED event. @@ -246,7 +253,10 @@ class ShareMode(Mode): self.history.in_progress_count += 1 self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_share_started_title'), strings._('systray_share_started_message')) + self.system_tray.showMessage( + strings._("systray_share_started_title"), + strings._("systray_share_started_message"), + ) def handle_request_progress(self, event): """ @@ -256,7 +266,10 @@ class ShareMode(Mode): # Is the download complete? if event["data"]["bytes"] == self.web.share_mode.filesize: - self.system_tray.showMessage(strings._('systray_share_completed_title'), strings._('systray_share_completed_message')) + self.system_tray.showMessage( + strings._("systray_share_completed_title"), + strings._("systray_share_completed_message"), + ) # Update completed and in progress labels self.history.completed_count += 1 @@ -265,10 +278,10 @@ class ShareMode(Mode): self.history.update_in_progress() # Close on finish? - if self.common.settings.get('close_after_first_download'): + if self.common.settings.get("close_after_first_download"): self.server_status.stop_server() self.status_bar.clearMessage() - self.server_status_label.setText(strings._('closing_automatically')) + self.server_status_label.setText(strings._("closing_automatically")) else: if self.server_status.status == self.server_status.STATUS_STOPPED: self.history.cancel(event["data"]["id"]) @@ -284,7 +297,10 @@ class ShareMode(Mode): # Update in progress count self.history.in_progress_count -= 1 self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_share_canceled_title'), strings._('systray_share_canceled_message')) + self.system_tray.showMessage( + strings._("systray_share_canceled_title"), + strings._("systray_share_canceled_message"), + ) def on_reload_settings(self): """ @@ -296,7 +312,7 @@ class ShareMode(Mode): self.info_label.show() def update_primary_action(self): - self.common.log('ShareMode', 'update_primary_action') + self.common.log("ShareMode", "update_primary_action") # Show or hide primary action layout file_count = self.file_selection.file_list.count() @@ -312,9 +328,15 @@ class ShareMode(Mode): total_size_readable = self.common.human_readable_filesize(total_size_bytes) if file_count > 1: - self.info_label.setText(strings._('gui_file_info').format(file_count, total_size_readable)) + self.info_label.setText( + strings._("gui_file_info").format(file_count, total_size_readable) + ) else: - self.info_label.setText(strings._('gui_file_info_single').format(file_count, total_size_readable)) + self.info_label.setText( + strings._("gui_file_info_single").format( + file_count, total_size_readable + ) + ) else: self.primary_action.hide() @@ -325,6 +347,8 @@ class ShareMode(Mode): Set the info counters back to zero. """ self.history.reset() + self.toggle_history.indicator_count = 0 + self.toggle_history.update_indicator() @staticmethod def _compute_total_size(filenames): @@ -347,8 +371,8 @@ class ZipProgressBar(QtWidgets.QProgressBar): self.setMaximumHeight(20) self.setMinimumWidth(200) self.setValue(0) - self.setFormat(strings._('zip_progress_bar_format')) - self.setStyleSheet(self.common.css['share_zip_progess_bar']) + self.setFormat(strings._("zip_progress_bar_format")) + self.setStyleSheet(self.common.css["share_zip_progess_bar"]) self._total_files_size = total_files_size self._processed_size = 0 diff --git a/onionshare_gui/mode/share_mode/threads.py b/onionshare_gui/mode/share_mode/threads.py index 24e2c242..414c7be1 100644 --- a/onionshare_gui/mode/share_mode/threads.py +++ b/onionshare_gui/mode/share_mode/threads.py @@ -24,13 +24,14 @@ class CompressThread(QtCore.QThread): """ Compresses files to be shared """ + success = QtCore.pyqtSignal() error = QtCore.pyqtSignal(str) def __init__(self, mode): super(CompressThread, self).__init__() self.mode = mode - self.mode.common.log('CompressThread', '__init__') + self.mode.common.log("CompressThread", "__init__") # prepare files to share def set_processed_size(self, x): @@ -38,21 +39,21 @@ class CompressThread(QtCore.QThread): self.mode._zip_progress_bar.update_processed_size_signal.emit(x) def run(self): - self.mode.common.log('CompressThread', 'run') + self.mode.common.log("CompressThread", "run") try: - if self.mode.web.share_mode.set_file_info(self.mode.filenames, processed_size_callback=self.set_processed_size): - self.success.emit() - else: - # Cancelled - pass - - self.mode.app.cleanup_filenames += self.mode.web.share_mode.cleanup_filenames + self.mode.web.share_mode.set_file_info( + self.mode.filenames, processed_size_callback=self.set_processed_size + ) + self.success.emit() + self.mode.app.cleanup_filenames += ( + self.mode.web.share_mode.cleanup_filenames + ) except OSError as e: self.error.emit(e.strerror) def cancel(self): - self.mode.common.log('CompressThread', 'cancel') + self.mode.common.log("CompressThread", "cancel") # Let the Web and ZipWriter objects know that we're canceling compression early self.mode.web.cancel_compression = True diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py new file mode 100644 index 00000000..8cd2eca6 --- /dev/null +++ b/onionshare_gui/mode/website_mode/__init__.py @@ -0,0 +1,266 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com> + +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 <http://www.gnu.org/licenses/>. +""" +import os +import random +import string + +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings +from onionshare.onion import * +from onionshare.common import Common +from onionshare.web import Web + +from ..file_selection import FileSelection +from .. import Mode +from ..history import History, ToggleHistory +from ...widgets import Alert + + +class WebsiteMode(Mode): + """ + Parts of the main window UI for sharing files. + """ + + success = QtCore.pyqtSignal() + error = QtCore.pyqtSignal(str) + + def init(self): + """ + Custom initialization for ReceiveMode. + """ + # Create the Web object + self.web = Web(self.common, True, "website") + + # File selection + self.file_selection = FileSelection(self.common, self) + if self.filenames: + for filename in self.filenames: + self.file_selection.file_list.add_file(filename) + + # Server status + self.server_status.set_mode("website", 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) + self.server_status.server_canceled.connect(self.file_selection.server_stopped) + self.server_status.server_canceled.connect(self.update_primary_action) + self.file_selection.file_list.files_updated.connect(self.server_status.update) + self.file_selection.file_list.files_updated.connect(self.update_primary_action) + # Tell server_status about web, then update + self.server_status.web = self.web + self.server_status.update() + + # Filesize warning + self.filesize_warning = QtWidgets.QLabel() + self.filesize_warning.setWordWrap(True) + self.filesize_warning.setStyleSheet(self.common.css["share_filesize_warning"]) + self.filesize_warning.hide() + + # Download history + self.history = History( + self.common, + QtGui.QPixmap.fromImage( + QtGui.QImage( + self.common.get_resource_path("images/share_icon_transparent.png") + ) + ), + strings._("gui_website_mode_no_files"), + strings._("gui_all_modes_history"), + "website", + ) + self.history.in_progress_label.hide() + self.history.completed_label.hide() + self.history.hide() + + # Info label + self.info_label = QtWidgets.QLabel() + self.info_label.hide() + + # Toggle history + self.toggle_history = ToggleHistory( + self.common, + self, + self.history, + QtGui.QIcon(self.common.get_resource_path("images/share_icon_toggle.png")), + QtGui.QIcon( + self.common.get_resource_path("images/share_icon_toggle_selected.png") + ), + ) + + # Top bar + top_bar_layout = QtWidgets.QHBoxLayout() + top_bar_layout.addWidget(self.info_label) + top_bar_layout.addStretch() + top_bar_layout.addWidget(self.toggle_history) + + # Primary action layout + self.primary_action_layout.addWidget(self.filesize_warning) + self.primary_action.hide() + self.update_primary_action() + + # Main layout + self.main_layout = QtWidgets.QVBoxLayout() + self.main_layout.addLayout(top_bar_layout) + self.main_layout.addLayout(self.file_selection) + self.main_layout.addWidget(self.primary_action) + self.main_layout.addWidget(self.min_width_widget) + + # Wrapper layout + self.wrapper_layout = QtWidgets.QHBoxLayout() + self.wrapper_layout.addLayout(self.main_layout) + self.wrapper_layout.addWidget(self.history, stretch=1) + self.setLayout(self.wrapper_layout) + + # Always start with focus on file selection + self.file_selection.setFocus() + + def get_stop_server_autostop_timer_text(self): + """ + Return the string to put on the stop server button, if there's an auto-stop timer + """ + return strings._("gui_share_stop_server_autostop_timer") + + def autostop_timer_finished_should_stop_server(self): + """ + The auto-stop timer expired, should we stop the server? Returns a bool + """ + + self.server_status.stop_server() + self.server_status_label.setText(strings._("close_on_autostop_timer")) + return True + + def start_server_custom(self): + """ + Starting the server. + """ + # Reset web counters + self.web.website_mode.visit_count = 0 + self.web.reset_invalid_passwords() + + # Hide and reset the downloads if we have previously shared + self.reset_info_counters() + + def start_server_step2_custom(self): + """ + Step 2 in starting the server. Zipping up files. + """ + self.filenames = [] + for index in range(self.file_selection.file_list.count()): + self.filenames.append(self.file_selection.file_list.item(index).filename) + + # Continue + self.starting_server_step3.emit() + self.start_server_finished.emit() + + def start_server_step3_custom(self): + """ + Step 3 in starting the server. Display large filesize + warning, if applicable. + """ + self.web.website_mode.set_file_info(self.filenames) + self.success.emit() + + 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. + """ + + self.filesize_warning.hide() + self.history.completed_count = 0 + self.file_selection.file_list.adjustSize() + + def cancel_server_custom(self): + """ + Log that the server has been cancelled + """ + self.common.log("WebsiteMode", "cancel_server") + + def handle_tor_broke_custom(self): + """ + Connection to Tor broke. + """ + self.primary_action.hide() + + def on_reload_settings(self): + """ + 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.primary_action.show() + self.info_label.show() + + def update_primary_action(self): + self.common.log("WebsiteMode", "update_primary_action") + + # Show or hide primary action layout + file_count = self.file_selection.file_list.count() + if file_count > 0: + self.primary_action.show() + self.info_label.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 = self.common.human_readable_filesize(total_size_bytes) + + if file_count > 1: + self.info_label.setText( + strings._("gui_file_info").format(file_count, total_size_readable) + ) + else: + self.info_label.setText( + strings._("gui_file_info_single").format( + file_count, total_size_readable + ) + ) + + else: + self.primary_action.hide() + self.info_label.hide() + + def reset_info_counters(self): + """ + Set the info counters back to zero. + """ + self.history.reset() + self.toggle_history.indicator_count = 0 + self.toggle_history.update_indicator() + + @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 diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 17839669..4639ea13 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -25,6 +25,7 @@ from onionshare.web import Web from .mode.share_mode import ShareMode from .mode.receive_mode import ReceiveMode +from .mode.website_mode import WebsiteMode from .tor_connection_dialog import TorConnectionDialog from .settings_dialog import SettingsDialog @@ -32,19 +33,24 @@ from .widgets import Alert from .update_checker import UpdateThread from .server_status import ServerStatus + class OnionShareGui(QtWidgets.QMainWindow): """ OnionShareGui is the main window for the GUI that contains all of the GUI elements. """ - MODE_SHARE = 'share' - MODE_RECEIVE = 'receive' - def __init__(self, common, onion, qtapp, app, filenames, config=False, local_only=False): + MODE_SHARE = "share" + MODE_RECEIVE = "receive" + MODE_WEBSITE = "website" + + def __init__( + self, common, onion, qtapp, app, filenames, config=False, local_only=False + ): super(OnionShareGui, self).__init__() self.common = common - self.common.log('OnionShareGui', '__init__') + self.common.log("OnionShareGui", "__init__") self.setMinimumWidth(820) self.setMinimumHeight(660) @@ -55,8 +61,10 @@ class OnionShareGui(QtWidgets.QMainWindow): self.mode = self.MODE_SHARE - self.setWindowTitle('OnionShare') - self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) + self.setWindowTitle("OnionShare") + self.setWindowIcon( + QtGui.QIcon(self.common.get_resource_path("images/logo.png")) + ) # Load settings, if a custom config was passed in self.config = config @@ -69,50 +77,74 @@ class OnionShareGui(QtWidgets.QMainWindow): # System tray menu = QtWidgets.QMenu() - self.settings_action = menu.addAction(strings._('gui_settings_window_title')) + self.settings_action = menu.addAction(strings._("gui_settings_window_title")) self.settings_action.triggered.connect(self.open_settings) - self.help_action = menu.addAction(strings._('gui_settings_button_help')) + self.help_action = menu.addAction(strings._("gui_settings_button_help")) self.help_action.triggered.connect(lambda: SettingsDialog.help_clicked(self)) - exit_action = menu.addAction(strings._('systray_menu_exit')) + exit_action = menu.addAction(strings._("systray_menu_exit")) exit_action.triggered.connect(self.close) self.system_tray = QtWidgets.QSystemTrayIcon(self) # The convention is Mac systray icons are always grayscale - if self.common.platform == 'Darwin': - self.system_tray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo_grayscale.png'))) + if self.common.platform == "Darwin": + self.system_tray.setIcon( + QtGui.QIcon(self.common.get_resource_path("images/logo_grayscale.png")) + ) else: - self.system_tray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) + self.system_tray.setIcon( + QtGui.QIcon(self.common.get_resource_path("images/logo.png")) + ) self.system_tray.setContextMenu(menu) self.system_tray.show() # Mode switcher, to switch between share files and receive files - self.share_mode_button = QtWidgets.QPushButton(strings._('gui_mode_share_button')); + self.share_mode_button = QtWidgets.QPushButton( + strings._("gui_mode_share_button") + ) self.share_mode_button.setFixedHeight(50) self.share_mode_button.clicked.connect(self.share_mode_clicked) - self.receive_mode_button = QtWidgets.QPushButton(strings._('gui_mode_receive_button')); + self.receive_mode_button = QtWidgets.QPushButton( + strings._("gui_mode_receive_button") + ) self.receive_mode_button.setFixedHeight(50) self.receive_mode_button.clicked.connect(self.receive_mode_clicked) + self.website_mode_button = QtWidgets.QPushButton( + strings._("gui_mode_website_button") + ) + self.website_mode_button.setFixedHeight(50) + self.website_mode_button.clicked.connect(self.website_mode_clicked) self.settings_button = QtWidgets.QPushButton() self.settings_button.setDefault(False) self.settings_button.setFixedWidth(40) self.settings_button.setFixedHeight(50) - self.settings_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/settings.png')) ) + self.settings_button.setIcon( + QtGui.QIcon(self.common.get_resource_path("images/settings.png")) + ) self.settings_button.clicked.connect(self.open_settings) - self.settings_button.setStyleSheet(self.common.css['settings_button']) - mode_switcher_layout = QtWidgets.QHBoxLayout(); + self.settings_button.setStyleSheet(self.common.css["settings_button"]) + mode_switcher_layout = QtWidgets.QHBoxLayout() mode_switcher_layout.setSpacing(0) mode_switcher_layout.addWidget(self.share_mode_button) mode_switcher_layout.addWidget(self.receive_mode_button) + mode_switcher_layout.addWidget(self.website_mode_button) mode_switcher_layout.addWidget(self.settings_button) # Server status indicator on the status bar - self.server_status_image_stopped = QtGui.QImage(self.common.get_resource_path('images/server_stopped.png')) - self.server_status_image_working = QtGui.QImage(self.common.get_resource_path('images/server_working.png')) - self.server_status_image_started = QtGui.QImage(self.common.get_resource_path('images/server_started.png')) + self.server_status_image_stopped = QtGui.QImage( + self.common.get_resource_path("images/server_stopped.png") + ) + self.server_status_image_working = QtGui.QImage( + self.common.get_resource_path("images/server_working.png") + ) + self.server_status_image_started = QtGui.QImage( + self.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(self.common.css['server_status_indicator_label']) + self.server_status_label = QtWidgets.QLabel("") + self.server_status_label.setStyleSheet( + self.common.css["server_status_indicator_label"] + ) 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) @@ -122,17 +154,34 @@ class OnionShareGui(QtWidgets.QMainWindow): # Status bar self.status_bar = QtWidgets.QStatusBar() self.status_bar.setSizeGripEnabled(False) - self.status_bar.setStyleSheet(self.common.css['status_bar']) + self.status_bar.setStyleSheet(self.common.css["status_bar"]) self.status_bar.addPermanentWidget(self.server_status_indicator) self.setStatusBar(self.status_bar) # Share mode - self.share_mode = ShareMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames, self.local_only) + self.share_mode = ShareMode( + self.common, + qtapp, + app, + self.status_bar, + self.server_status_label, + self.system_tray, + filenames, + self.local_only, + ) 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) - self.share_mode.start_server_finished.connect(self.update_server_status_indicator) - self.share_mode.stop_server_finished.connect(self.update_server_status_indicator) + 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 + ) + self.share_mode.start_server_finished.connect( + self.update_server_status_indicator + ) + self.share_mode.stop_server_finished.connect( + self.update_server_status_indicator + ) self.share_mode.stop_server_finished.connect(self.stop_server_finished) self.share_mode.start_server_finished.connect(self.clear_message) self.share_mode.server_status.button_clicked.connect(self.clear_message) @@ -141,19 +190,70 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.set_server_active.connect(self.set_server_active) # Receive mode - self.receive_mode = ReceiveMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, None, self.local_only) + self.receive_mode = ReceiveMode( + self.common, + qtapp, + app, + self.status_bar, + self.server_status_label, + self.system_tray, + None, + self.local_only, + ) self.receive_mode.init() - self.receive_mode.server_status.server_started.connect(self.update_server_status_indicator) - self.receive_mode.server_status.server_stopped.connect(self.update_server_status_indicator) - self.receive_mode.start_server_finished.connect(self.update_server_status_indicator) - self.receive_mode.stop_server_finished.connect(self.update_server_status_indicator) + self.receive_mode.server_status.server_started.connect( + self.update_server_status_indicator + ) + self.receive_mode.server_status.server_stopped.connect( + self.update_server_status_indicator + ) + self.receive_mode.start_server_finished.connect( + self.update_server_status_indicator + ) + self.receive_mode.stop_server_finished.connect( + self.update_server_status_indicator + ) self.receive_mode.stop_server_finished.connect(self.stop_server_finished) self.receive_mode.start_server_finished.connect(self.clear_message) self.receive_mode.server_status.button_clicked.connect(self.clear_message) self.receive_mode.server_status.url_copied.connect(self.copy_url) - self.receive_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) + self.receive_mode.server_status.hidservauth_copied.connect( + self.copy_hidservauth + ) self.receive_mode.set_server_active.connect(self.set_server_active) + # Website mode + self.website_mode = WebsiteMode( + self.common, + qtapp, + app, + self.status_bar, + self.server_status_label, + self.system_tray, + filenames, + ) + self.website_mode.init() + self.website_mode.server_status.server_started.connect( + self.update_server_status_indicator + ) + self.website_mode.server_status.server_stopped.connect( + self.update_server_status_indicator + ) + self.website_mode.start_server_finished.connect( + self.update_server_status_indicator + ) + self.website_mode.stop_server_finished.connect( + self.update_server_status_indicator + ) + self.website_mode.stop_server_finished.connect(self.stop_server_finished) + self.website_mode.start_server_finished.connect(self.clear_message) + self.website_mode.server_status.button_clicked.connect(self.clear_message) + self.website_mode.server_status.url_copied.connect(self.copy_url) + self.website_mode.server_status.hidservauth_copied.connect( + self.copy_hidservauth + ) + self.website_mode.set_server_active.connect(self.set_server_active) + self.update_mode_switcher() self.update_server_status_indicator() @@ -162,6 +262,7 @@ class OnionShareGui(QtWidgets.QMainWindow): contents_layout.setContentsMargins(10, 0, 10, 0) contents_layout.addWidget(self.receive_mode) contents_layout.addWidget(self.share_mode) + contents_layout.addWidget(self.website_mode) layout = QtWidgets.QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) @@ -197,62 +298,149 @@ class OnionShareGui(QtWidgets.QMainWindow): # Based on the current mode, switch the mode switcher button styles, # and show and hide widgets to switch modes if self.mode == self.MODE_SHARE: - self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) - self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) + self.share_mode_button.setStyleSheet( + self.common.css["mode_switcher_selected_style"] + ) + self.receive_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) + self.website_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) self.receive_mode.hide() self.share_mode.show() + self.website_mode.hide() + elif self.mode == self.MODE_WEBSITE: + self.share_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) + self.receive_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) + self.website_mode_button.setStyleSheet( + self.common.css["mode_switcher_selected_style"] + ) + + self.receive_mode.hide() + self.share_mode.hide() + self.website_mode.show() else: - self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) - self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) + self.share_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) + self.receive_mode_button.setStyleSheet( + self.common.css["mode_switcher_selected_style"] + ) + self.website_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) self.share_mode.hide() self.receive_mode.show() + self.website_mode.hide() self.update_server_status_indicator() def share_mode_clicked(self): if self.mode != self.MODE_SHARE: - self.common.log('OnionShareGui', 'share_mode_clicked') + self.common.log("OnionShareGui", "share_mode_clicked") self.mode = self.MODE_SHARE self.update_mode_switcher() def receive_mode_clicked(self): if self.mode != self.MODE_RECEIVE: - self.common.log('OnionShareGui', 'receive_mode_clicked') + self.common.log("OnionShareGui", "receive_mode_clicked") self.mode = self.MODE_RECEIVE self.update_mode_switcher() + def website_mode_clicked(self): + if self.mode != self.MODE_WEBSITE: + self.common.log("OnionShareGui", "website_mode_clicked") + self.mode = self.MODE_WEBSITE + self.update_mode_switcher() + def update_server_status_indicator(self): # Set the status image if self.mode == self.MODE_SHARE: # Share mode if self.share_mode.server_status.status == ServerStatus.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_share_stopped')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_stopped) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_share_stopped") + ) elif self.share_mode.server_status.status == ServerStatus.STATUS_WORKING: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_working) + ) if self.share_mode.server_status.autostart_timer_datetime: - self.server_status_label.setText(strings._('gui_status_indicator_share_scheduled')) + self.server_status_label.setText( + strings._("gui_status_indicator_share_scheduled") + ) else: - self.server_status_label.setText(strings._('gui_status_indicator_share_working')) + self.server_status_label.setText( + strings._("gui_status_indicator_share_working") + ) elif self.share_mode.server_status.status == ServerStatus.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_share_started')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_started) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_share_started") + ) + elif self.mode == self.MODE_WEBSITE: + # Website mode + if self.website_mode.server_status.status == ServerStatus.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_share_stopped") + ) + elif self.website_mode.server_status.status == ServerStatus.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_share_working") + ) + elif self.website_mode.server_status.status == ServerStatus.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_share_started") + ) else: # Receive mode if self.receive_mode.server_status.status == ServerStatus.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_receive_stopped')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_stopped) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_receive_stopped") + ) elif self.receive_mode.server_status.status == ServerStatus.STATUS_WORKING: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_working) + ) if self.receive_mode.server_status.autostart_timer_datetime: - self.server_status_label.setText(strings._('gui_status_indicator_receive_scheduled')) + self.server_status_label.setText( + strings._("gui_status_indicator_receive_scheduled") + ) else: - self.server_status_label.setText(strings._('gui_status_indicator_receive_working')) + self.server_status_label.setText( + strings._("gui_status_indicator_receive_working") + ) elif self.receive_mode.server_status.status == ServerStatus.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_receive_started')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_started) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_receive_started") + ) def stop_server_finished(self): # When the server stopped, cleanup the ephemeral onion service @@ -263,12 +451,22 @@ class OnionShareGui(QtWidgets.QMainWindow): If the user cancels before Tor finishes connecting, ask if they want to quit, or open settings. """ - self.common.log('OnionShareGui', '_tor_connection_canceled') + self.common.log("OnionShareGui", "_tor_connection_canceled") def ask(): - a = Alert(self.common, strings._('gui_tor_connection_ask'), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) - settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings')) - quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit')) + a = Alert( + self.common, + strings._("gui_tor_connection_ask"), + QtWidgets.QMessageBox.Question, + buttons=QtWidgets.QMessageBox.NoButton, + autostart=False, + ) + settings_button = QtWidgets.QPushButton( + strings._("gui_tor_connection_ask_open_settings") + ) + quit_button = QtWidgets.QPushButton( + strings._("gui_tor_connection_ask_quit") + ) a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole) a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole) a.setDefaultButton(settings_button) @@ -276,12 +474,18 @@ class OnionShareGui(QtWidgets.QMainWindow): if a.clickedButton() == settings_button: # Open settings - self.common.log('OnionShareGui', '_tor_connection_canceled', 'Settings button clicked') + self.common.log( + "OnionShareGui", + "_tor_connection_canceled", + "Settings button clicked", + ) self.open_settings() if a.clickedButton() == quit_button: # Quit - self.common.log('OnionShareGui', '_tor_connection_canceled', 'Quit button clicked') + self.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) @@ -293,7 +497,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ The TorConnectionDialog wants to open the Settings dialog """ - self.common.log('OnionShareGui', '_tor_connection_open_settings') + self.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) @@ -302,10 +506,12 @@ class OnionShareGui(QtWidgets.QMainWindow): """ Open the SettingsDialog. """ - self.common.log('OnionShareGui', 'open_settings') + self.common.log("OnionShareGui", "open_settings") def reload_settings(): - self.common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading') + self.common.log( + "OnionShareGui", "open_settings", "settings have changed, reloading" + ) self.common.settings.load() # We might've stopped the main requests timer if a Tor connection failed. @@ -317,35 +523,48 @@ class OnionShareGui(QtWidgets.QMainWindow): self.timer.start(500) self.share_mode.on_reload_settings() self.receive_mode.on_reload_settings() + self.website_mode.on_reload_settings() self.status_bar.clearMessage() # If we switched off the auto-stop timer setting, ensure the widget is hidden. - if not self.common.settings.get('autostop_timer'): + if not self.common.settings.get("autostop_timer"): self.share_mode.server_status.autostop_timer_container.hide() self.receive_mode.server_status.autostop_timer_container.hide() + self.website_mode.server_status.autostop_timer_container.hide() # If we switched off the auto-start timer setting, ensure the widget is hidden. - if not self.common.settings.get('autostart_timer'): + if not self.common.settings.get("autostart_timer"): self.share_mode.server_status.autostart_timer_datetime = None self.receive_mode.server_status.autostart_timer_datetime = None + self.website_mode.server_status.autostart_timer_datetime = None self.share_mode.server_status.autostart_timer_container.hide() self.receive_mode.server_status.autostart_timer_container.hide() + self.website_mode.server_status.autostart_timer_container.hide() - d = SettingsDialog(self.common, self.onion, self.qtapp, self.config, self.local_only) + d = SettingsDialog( + self.common, self.onion, self.qtapp, self.config, self.local_only + ) d.settings_saved.connect(reload_settings) d.exec_() # When settings close, refresh the server status UI self.share_mode.server_status.update() self.receive_mode.server_status.update() + self.website_mode.server_status.update() def check_for_updates(self): """ Check for updates in a new thread, if enabled. """ - if self.common.platform == 'Windows' or self.common.platform == 'Darwin': - if self.common.settings.get('use_autoupdate'): + if self.common.platform == "Windows" or self.common.platform == "Darwin": + if self.common.settings.get("use_autoupdate"): + def update_available(update_url, installed_version, latest_version): - Alert(self.common, strings._("update_available").format(update_url, installed_version, latest_version)) + Alert( + self.common, + strings._("update_available").format( + update_url, installed_version, latest_version + ), + ) self.update_thread = UpdateThread(self.common, self.onion, self.config) self.update_thread.update_available.connect(update_available) @@ -362,15 +581,21 @@ class OnionShareGui(QtWidgets.QMainWindow): # Have we lost connection to Tor somehow? if not self.onion.is_authenticated(): self.timer.stop() - self.status_bar.showMessage(strings._('gui_tor_connection_lost')) - self.system_tray.showMessage(strings._('gui_tor_connection_lost'), strings._('gui_tor_connection_error_settings')) + self.status_bar.showMessage(strings._("gui_tor_connection_lost")) + self.system_tray.showMessage( + strings._("gui_tor_connection_lost"), + strings._("gui_tor_connection_error_settings"), + ) self.share_mode.handle_tor_broke() self.receive_mode.handle_tor_broke() + self.website_mode.handle_tor_broke() # Process events from the web object if self.mode == self.MODE_SHARE: mode = self.share_mode + elif self.mode == self.MODE_WEBSITE: + mode = self.website_mode else: mode = self.receive_mode @@ -412,12 +637,41 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == Web.REQUEST_UPLOAD_CANCELED: mode.handle_request_upload_canceled(event) + elif event["type"] == Web.REQUEST_INDIVIDUAL_FILE_STARTED: + mode.handle_request_individual_file_started(event) + + elif event["type"] == Web.REQUEST_INDIVIDUAL_FILE_PROGRESS: + mode.handle_request_individual_file_progress(event) + + elif event["type"] == Web.REQUEST_INDIVIDUAL_FILE_CANCELED: + mode.handle_request_individual_file_canceled(event) + if event["type"] == Web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE: - Alert(self.common, strings._('error_cannot_create_data_dir').format(event["data"]["receive_mode_dir"])) + Alert( + self.common, + strings._("error_cannot_create_data_dir").format( + event["data"]["receive_mode_dir"] + ), + ) if event["type"] == Web.REQUEST_OTHER: - if event["path"] != '/favicon.ico' and event["path"] != "/{}/shutdown".format(mode.web.shutdown_slug): - self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.error404_count, strings._('other_page_loaded'), event["path"])) + if event["path"] != "/favicon.ico" and event[ + "path" + ] != "/{}/shutdown".format(mode.web.shutdown_password): + self.status_bar.showMessage( + "{0:s}: {1:s}".format( + strings._("other_page_loaded"), event["path"] + ) + ) + + if event["type"] == Web.REQUEST_INVALID_PASSWORD: + self.status_bar.showMessage( + "[#{0:d}] {1:s}: {2:s}".format( + mode.web.invalid_passwords_count, + strings._("incorrect_password"), + event["data"], + ) + ) mode.timer_callback() @@ -425,15 +679,20 @@ class OnionShareGui(QtWidgets.QMainWindow): """ When the URL gets copied to the clipboard, display this in the status bar. """ - self.common.log('OnionShareGui', 'copy_url') - self.system_tray.showMessage(strings._('gui_copied_url_title'), strings._('gui_copied_url')) + self.common.log("OnionShareGui", "copy_url") + self.system_tray.showMessage( + strings._("gui_copied_url_title"), strings._("gui_copied_url") + ) def copy_hidservauth(self): """ When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar. """ - self.common.log('OnionShareGui', 'copy_hidservauth') - self.system_tray.showMessage(strings._('gui_copied_hidservauth_title'), strings._('gui_copied_hidservauth')) + self.common.log("OnionShareGui", "copy_hidservauth") + self.system_tray.showMessage( + strings._("gui_copied_hidservauth_title"), + strings._("gui_copied_hidservauth"), + ) def clear_message(self): """ @@ -450,35 +709,50 @@ class OnionShareGui(QtWidgets.QMainWindow): if self.mode == self.MODE_SHARE: self.share_mode_button.show() self.receive_mode_button.hide() + self.website_mode_button.hide() + elif self.mode == self.MODE_WEBSITE: + self.share_mode_button.hide() + self.receive_mode_button.hide() + self.website_mode_button.show() else: self.share_mode_button.hide() self.receive_mode_button.show() + self.website_mode_button.hide() else: self.settings_button.show() self.share_mode_button.show() self.receive_mode_button.show() + self.website_mode_button.show() # Disable settings menu action when server is active self.settings_action.setEnabled(not active) def closeEvent(self, e): - self.common.log('OnionShareGui', 'closeEvent') + self.common.log("OnionShareGui", "closeEvent") + self.system_tray.hide() try: if self.mode == OnionShareGui.MODE_SHARE: server_status = self.share_mode.server_status + if self.mode == OnionShareGui.MODE_WEBSITE: + server_status = self.website_mode.server_status else: server_status = self.receive_mode.server_status if server_status.status != server_status.STATUS_STOPPED: - self.common.log('OnionShareGui', 'closeEvent, opening warning dialog') + self.common.log("OnionShareGui", "closeEvent, opening warning dialog") dialog = QtWidgets.QMessageBox() - dialog.setWindowTitle(strings._('gui_quit_title')) + dialog.setWindowTitle(strings._("gui_quit_title")) if self.mode == OnionShareGui.MODE_SHARE: - dialog.setText(strings._('gui_share_quit_warning')) + dialog.setText(strings._("gui_share_quit_warning")) else: - dialog.setText(strings._('gui_receive_quit_warning')) + dialog.setText(strings._("gui_receive_quit_warning")) dialog.setIcon(QtWidgets.QMessageBox.Critical) - quit_button = dialog.addButton(strings._('gui_quit_warning_quit'), QtWidgets.QMessageBox.YesRole) - dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit'), QtWidgets.QMessageBox.NoRole) + quit_button = dialog.addButton( + strings._("gui_quit_warning_quit"), QtWidgets.QMessageBox.YesRole + ) + dont_quit_button = dialog.addButton( + strings._("gui_quit_warning_dont_quit"), + QtWidgets.QMessageBox.NoRole, + ) dialog.setDefaultButton(dont_quit_button) reply = dialog.exec_() diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 0c51119e..4ce1f5d2 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -25,10 +25,12 @@ from onionshare import strings from .widgets import Alert + class ServerStatus(QtWidgets.QWidget): """ The server status chunk of the GUI. """ + server_started = QtCore.pyqtSignal() server_started_finished = QtCore.pyqtSignal() server_stopped = QtCore.pyqtSignal() @@ -37,8 +39,9 @@ class ServerStatus(QtWidgets.QWidget): url_copied = QtCore.pyqtSignal() hidservauth_copied = QtCore.pyqtSignal() - MODE_SHARE = 'share' - MODE_RECEIVE = 'receive' + MODE_SHARE = "share" + MODE_RECEIVE = "receive" + MODE_WEBSITE = "website" STATUS_STOPPED = 0 STATUS_WORKING = 1 @@ -50,7 +53,7 @@ class ServerStatus(QtWidgets.QWidget): self.common = common self.status = self.STATUS_STOPPED - self.mode = None # Gets set in self.set_mode + self.mode = None # Gets set in self.set_mode self.qtapp = qtapp self.app = app @@ -62,19 +65,31 @@ class ServerStatus(QtWidgets.QWidget): self.resizeEvent(None) # Auto-start timer layout - self.autostart_timer_label = QtWidgets.QLabel(strings._('gui_settings_autostart_timer')) + self.autostart_timer_label = QtWidgets.QLabel( + strings._("gui_settings_autostart_timer") + ) self.autostart_timer_widget = QtWidgets.QDateTimeEdit() self.autostart_timer_widget.setDisplayFormat("hh:mm A MMM d, yy") if self.local_only: # For testing - self.autostart_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(15)) - self.autostart_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime()) + self.autostart_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(15) + ) + self.autostart_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime() + ) else: # Set proposed timer to be 5 minutes into the future - self.autostart_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + self.autostart_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(300) + ) # Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 60s from now - self.autostart_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60)) - self.autostart_timer_widget.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection) + self.autostart_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime().addSecs(60) + ) + self.autostart_timer_widget.setCurrentSection( + QtWidgets.QDateTimeEdit.MinuteSection + ) autostart_timer_layout = QtWidgets.QHBoxLayout() autostart_timer_layout.addWidget(self.autostart_timer_label) autostart_timer_layout.addWidget(self.autostart_timer_widget) @@ -87,19 +102,31 @@ class ServerStatus(QtWidgets.QWidget): self.autostart_timer_container.hide() # Auto-stop timer layout - self.autostop_timer_label = QtWidgets.QLabel(strings._('gui_settings_autostop_timer')) + self.autostop_timer_label = QtWidgets.QLabel( + strings._("gui_settings_autostop_timer") + ) self.autostop_timer_widget = QtWidgets.QDateTimeEdit() self.autostop_timer_widget.setDisplayFormat("hh:mm A MMM d, yy") if self.local_only: # For testing - self.autostop_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(15)) - self.autostop_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime()) + self.autostop_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(15) + ) + self.autostop_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime() + ) else: # Set proposed timer to be 5 minutes into the future - self.autostop_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + self.autostop_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(300) + ) # Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 60s from now - self.autostop_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60)) - self.autostop_timer_widget.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection) + self.autostop_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime().addSecs(60) + ) + self.autostop_timer_widget.setCurrentSection( + QtWidgets.QDateTimeEdit.MinuteSection + ) autostop_timer_layout = QtWidgets.QHBoxLayout() autostop_timer_layout.addWidget(self.autostop_timer_label) autostop_timer_layout.addWidget(self.autostop_timer_widget) @@ -124,16 +151,20 @@ class ServerStatus(QtWidgets.QWidget): self.url.setFont(url_font) self.url.setWordWrap(True) self.url.setMinimumSize(self.url.sizeHint()) - self.url.setStyleSheet(self.common.css['server_status_url']) + self.url.setStyleSheet(self.common.css["server_status_url"]) - self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url')) + self.copy_url_button = QtWidgets.QPushButton(strings._("gui_copy_url")) self.copy_url_button.setFlat(True) - self.copy_url_button.setStyleSheet(self.common.css['server_status_url_buttons']) + self.copy_url_button.setStyleSheet(self.common.css["server_status_url_buttons"]) self.copy_url_button.setMinimumHeight(65) self.copy_url_button.clicked.connect(self.copy_url) - self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth')) + self.copy_hidservauth_button = QtWidgets.QPushButton( + strings._("gui_copy_hidservauth") + ) self.copy_hidservauth_button.setFlat(True) - self.copy_hidservauth_button.setStyleSheet(self.common.css['server_status_url_buttons']) + self.copy_hidservauth_button.setStyleSheet( + self.common.css["server_status_url_buttons"] + ) self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth) url_buttons_layout = QtWidgets.QHBoxLayout() url_buttons_layout.addWidget(self.copy_url_button) @@ -159,7 +190,9 @@ class ServerStatus(QtWidgets.QWidget): """ self.mode = share_mode - if self.mode == ServerStatus.MODE_SHARE: + if (self.mode == ServerStatus.MODE_SHARE) or ( + self.mode == ServerStatus.MODE_WEBSITE + ): self.file_selection = file_selection self.update() @@ -170,7 +203,7 @@ class ServerStatus(QtWidgets.QWidget): """ try: # Wrap the URL label - url_length=len(self.get_url()) + url_length = len(self.get_url()) if url_length > 60: width = self.frameGeometry().width() if width < 530: @@ -185,17 +218,25 @@ class ServerStatus(QtWidgets.QWidget): """ Reset the auto-start timer in the UI after stopping a share """ - self.autostart_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + self.autostart_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(300) + ) if not self.local_only: - self.autostart_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60)) + self.autostart_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime().addSecs(60) + ) def autostop_timer_reset(self): """ Reset the auto-stop timer in the UI after stopping a share """ - self.autostop_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + self.autostop_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(300) + ) if not self.local_only: - self.autostop_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60)) + self.autostop_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime().addSecs(60) + ) def show_url(self): """ @@ -203,24 +244,38 @@ class ServerStatus(QtWidgets.QWidget): """ self.url_description.show() - info_image = self.common.get_resource_path('images/info.png') + info_image = self.common.get_resource_path("images/info.png") if self.mode == ServerStatus.MODE_SHARE: - self.url_description.setText(strings._('gui_share_url_description').format(info_image)) + self.url_description.setText( + strings._("gui_share_url_description").format(info_image) + ) + elif self.mode == ServerStatus.MODE_WEBSITE: + self.url_description.setText( + strings._("gui_website_url_description").format(info_image) + ) else: - self.url_description.setText(strings._('gui_receive_url_description').format(info_image)) + self.url_description.setText( + strings._("gui_receive_url_description").format(info_image) + ) # Show a Tool Tip explaining the lifecycle of this URL - if self.common.settings.get('save_private_key'): - if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get('close_after_first_download'): - self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent')) + if self.common.settings.get("save_private_key"): + if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get( + "close_after_first_download" + ): + self.url_description.setToolTip( + strings._("gui_url_label_onetime_and_persistent") + ) else: - self.url_description.setToolTip(strings._('gui_url_label_persistent')) + self.url_description.setToolTip(strings._("gui_url_label_persistent")) else: - if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get('close_after_first_download'): - self.url_description.setToolTip(strings._('gui_url_label_onetime')) + if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get( + "close_after_first_download" + ): + self.url_description.setToolTip(strings._("gui_url_label_onetime")) else: - self.url_description.setToolTip(strings._('gui_url_label_stay_open')) + self.url_description.setToolTip(strings._("gui_url_label_stay_open")) self.url.setText(self.get_url()) self.url.show() @@ -237,17 +292,20 @@ class ServerStatus(QtWidgets.QWidget): """ # Set the URL fields if self.status == self.STATUS_STARTED: + # The backend Onion may have saved new settings, such as the private key. + # Reload the settings before saving new ones. + self.common.settings.load() self.show_url() - if self.common.settings.get('save_private_key'): - if not self.common.settings.get('slug'): - self.common.settings.set('slug', self.web.slug) + if self.common.settings.get("save_private_key"): + if not self.common.settings.get("password"): + self.common.settings.set("password", self.web.password) self.common.settings.save() - if self.common.settings.get('autostart_timer'): + if self.common.settings.get("autostart_timer"): self.autostart_timer_container.hide() - if self.common.settings.get('autostop_timer'): + if self.common.settings.get("autostop_timer"): self.autostop_timer_container.hide() else: self.url_description.hide() @@ -256,53 +314,91 @@ class ServerStatus(QtWidgets.QWidget): self.copy_hidservauth_button.hide() # Button - if self.mode == ServerStatus.MODE_SHARE and self.file_selection.get_num_files() == 0: + if ( + self.mode == ServerStatus.MODE_SHARE + and self.file_selection.get_num_files() == 0 + ): + self.server_button.hide() + elif ( + self.mode == ServerStatus.MODE_WEBSITE + and self.file_selection.get_num_files() == 0 + ): self.server_button.hide() else: self.server_button.show() if self.status == self.STATUS_STOPPED: - self.server_button.setStyleSheet(self.common.css['server_status_button_stopped']) + self.server_button.setStyleSheet( + self.common.css["server_status_button_stopped"] + ) self.server_button.setEnabled(True) if self.mode == ServerStatus.MODE_SHARE: - self.server_button.setText(strings._('gui_share_start_server')) + self.server_button.setText(strings._("gui_share_start_server")) + elif self.mode == ServerStatus.MODE_WEBSITE: + self.server_button.setText(strings._("gui_share_start_server")) else: - self.server_button.setText(strings._('gui_receive_start_server')) - self.server_button.setToolTip('') - if self.common.settings.get('autostart_timer'): + self.server_button.setText(strings._("gui_receive_start_server")) + self.server_button.setToolTip("") + if self.common.settings.get("autostart_timer"): self.autostart_timer_container.show() - if self.common.settings.get('autostop_timer'): + if self.common.settings.get("autostop_timer"): self.autostop_timer_container.show() elif self.status == self.STATUS_STARTED: - self.server_button.setStyleSheet(self.common.css['server_status_button_started']) + self.server_button.setStyleSheet( + self.common.css["server_status_button_started"] + ) self.server_button.setEnabled(True) if self.mode == ServerStatus.MODE_SHARE: - self.server_button.setText(strings._('gui_share_stop_server')) + self.server_button.setText(strings._("gui_share_stop_server")) + elif self.mode == ServerStatus.MODE_WEBSITE: + self.server_button.setText(strings._("gui_share_stop_server")) else: - self.server_button.setText(strings._('gui_receive_stop_server')) - if self.common.settings.get('autostart_timer'): + self.server_button.setText(strings._("gui_receive_stop_server")) + if self.common.settings.get("autostart_timer"): self.autostart_timer_container.hide() - if self.common.settings.get('autostop_timer'): + if self.common.settings.get("autostop_timer"): self.autostop_timer_container.hide() - self.server_button.setToolTip(strings._('gui_stop_server_autostop_timer_tooltip').format(self.autostop_timer_widget.dateTime().toString("h:mm AP, MMMM dd, yyyy"))) + self.server_button.setToolTip( + strings._("gui_stop_server_autostop_timer_tooltip").format( + self.autostop_timer_widget.dateTime().toString( + "h:mm AP, MMMM dd, yyyy" + ) + ) + ) elif self.status == self.STATUS_WORKING: - self.server_button.setStyleSheet(self.common.css['server_status_button_working']) + self.server_button.setStyleSheet( + self.common.css["server_status_button_working"] + ) self.server_button.setEnabled(True) if self.autostart_timer_datetime: self.autostart_timer_container.hide() - self.server_button.setToolTip(strings._('gui_start_server_autostart_timer_tooltip').format(self.autostart_timer_widget.dateTime().toString("h:mm AP, MMMM dd, yyyy"))) + self.server_button.setToolTip( + strings._("gui_start_server_autostart_timer_tooltip").format( + self.autostart_timer_widget.dateTime().toString( + "h:mm AP, MMMM dd, yyyy" + ) + ) + ) else: - self.server_button.setText(strings._('gui_please_wait')) - if self.common.settings.get('autostop_timer'): + self.server_button.setText(strings._("gui_please_wait")) + if self.common.settings.get("autostop_timer"): self.autostop_timer_container.hide() else: - self.server_button.setStyleSheet(self.common.css['server_status_button_working']) + self.server_button.setStyleSheet( + self.common.css["server_status_button_working"] + ) self.server_button.setEnabled(False) - self.server_button.setText(strings._('gui_please_wait')) - if self.common.settings.get('autostart_timer'): + self.server_button.setText(strings._("gui_please_wait")) + if self.common.settings.get("autostart_timer"): self.autostart_timer_container.hide() - self.server_button.setToolTip(strings._('gui_start_server_autostart_timer_tooltip').format(self.autostart_timer_widget.dateTime().toString("h:mm AP, MMMM dd, yyyy"))) - if self.common.settings.get('autostop_timer'): + self.server_button.setToolTip( + strings._("gui_start_server_autostart_timer_tooltip").format( + self.autostart_timer_widget.dateTime().toString( + "h:mm AP, MMMM dd, yyyy" + ) + ) + ) + if self.common.settings.get("autostop_timer"): self.autostop_timer_container.hide() def server_button_clicked(self): @@ -311,28 +407,60 @@ class ServerStatus(QtWidgets.QWidget): """ if self.status == self.STATUS_STOPPED: can_start = True - if self.common.settings.get('autostart_timer'): + if self.common.settings.get("autostart_timer"): if self.local_only: - self.autostart_timer_datetime = self.autostart_timer_widget.dateTime().toPyDateTime() + self.autostart_timer_datetime = ( + self.autostart_timer_widget.dateTime().toPyDateTime() + ) else: - self.autostart_timer_datetime = self.autostart_timer_widget.dateTime().toPyDateTime().replace(second=0, microsecond=0) + self.autostart_timer_datetime = ( + self.autostart_timer_widget.dateTime() + .toPyDateTime() + .replace(second=0, microsecond=0) + ) # If the timer has actually passed already before the user hit Start, refuse to start the server. - if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.autostart_timer_datetime: + if ( + QtCore.QDateTime.currentDateTime().toPyDateTime() + > self.autostart_timer_datetime + ): can_start = False - Alert(self.common, strings._('gui_server_autostart_timer_expired'), QtWidgets.QMessageBox.Warning) - if self.common.settings.get('autostop_timer'): + Alert( + self.common, + strings._("gui_server_autostart_timer_expired"), + QtWidgets.QMessageBox.Warning, + ) + if self.common.settings.get("autostop_timer"): if self.local_only: - self.autostop_timer_datetime = self.autostop_timer_widget.dateTime().toPyDateTime() + self.autostop_timer_datetime = ( + self.autostop_timer_widget.dateTime().toPyDateTime() + ) else: # Get the timer chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen - self.autostop_timer_datetime = self.autostop_timer_widget.dateTime().toPyDateTime().replace(second=0, microsecond=0) + self.autostop_timer_datetime = ( + self.autostop_timer_widget.dateTime() + .toPyDateTime() + .replace(second=0, microsecond=0) + ) # If the timer has actually passed already before the user hit Start, refuse to start the server. - if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.autostop_timer_datetime: + if ( + QtCore.QDateTime.currentDateTime().toPyDateTime() + > self.autostop_timer_datetime + ): can_start = False - Alert(self.common, strings._('gui_server_autostop_timer_expired'), QtWidgets.QMessageBox.Warning) - if self.common.settings.get('autostart_timer'): + Alert( + self.common, + strings._("gui_server_autostop_timer_expired"), + QtWidgets.QMessageBox.Warning, + ) + if self.common.settings.get("autostart_timer"): if self.autostop_timer_datetime <= self.autostart_timer_datetime: - Alert(self.common, strings._('gui_autostop_timer_cant_be_earlier_than_autostart_timer'), QtWidgets.QMessageBox.Warning) + Alert( + self.common, + strings._( + "gui_autostop_timer_cant_be_earlier_than_autostart_timer" + ), + QtWidgets.QMessageBox.Warning, + ) can_start = False if can_start: self.start_server() @@ -373,7 +501,9 @@ class ServerStatus(QtWidgets.QWidget): """ Cancel the server. """ - self.common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup') + self.common.log( + "ServerStatus", "cancel_server", "Canceling the server mid-startup" + ) self.status = self.STATUS_WORKING self.autostart_timer_reset() self.autostop_timer_reset() @@ -409,8 +539,10 @@ class ServerStatus(QtWidgets.QWidget): """ Returns the OnionShare URL. """ - if self.common.settings.get('public_mode'): - url = 'http://{0:s}'.format(self.app.onion_host) + if self.common.settings.get("public_mode"): + url = "http://{0:s}".format(self.app.onion_host) else: - url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug) + url = "http://onionshare:{0:s}@{1:s}".format( + self.web.password, self.app.onion_host + ) return url diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 3c0b83f4..503e53a0 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -18,7 +18,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. """ from PyQt5 import QtCore, QtWidgets, QtGui -import sys, platform, datetime, re +import sys +import platform +import datetime +import re +import os from onionshare import strings, common from onionshare.settings import Settings @@ -28,10 +32,12 @@ from .widgets import Alert from .update_checker import * from .tor_connection_dialog import TorConnectionDialog + class SettingsDialog(QtWidgets.QDialog): """ Settings dialog. """ + settings_saved = QtCore.pyqtSignal() def __init__(self, common, onion, qtapp, config=False, local_only=False): @@ -39,7 +45,7 @@ class SettingsDialog(QtWidgets.QDialog): self.common = common - self.common.log('SettingsDialog', '__init__') + self.common.log("SettingsDialog", "__init__") self.onion = onion self.qtapp = qtapp @@ -47,19 +53,30 @@ class SettingsDialog(QtWidgets.QDialog): self.local_only = local_only self.setModal(True) - self.setWindowTitle(strings._('gui_settings_window_title')) - self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) + self.setWindowTitle(strings._("gui_settings_window_title")) + self.setWindowIcon( + QtGui.QIcon(self.common.get_resource_path("images/logo.png")) + ) self.system = platform.system() + # If ONIONSHARE_HIDE_TOR_SETTINGS=1, hide Tor settings in the dialog + self.hide_tor_settings = os.environ.get("ONIONSHARE_HIDE_TOR_SETTINGS") == "1" + # General settings - # Use a slug or not ('public mode') + # Use a password or not ('public mode') self.public_mode_checkbox = QtWidgets.QCheckBox() self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox")) - public_mode_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Public-Mode")) - public_mode_label.setStyleSheet(self.common.css['settings_whats_this']) + self.public_mode_checkbox.setText( + strings._("gui_settings_public_mode_checkbox") + ) + public_mode_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Public-Mode" + ) + ) + public_mode_label.setStyleSheet(self.common.css["settings_whats_this"]) public_mode_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) public_mode_label.setOpenExternalLinks(True) public_mode_label.setMinimumSize(public_mode_label.sizeHint()) @@ -67,16 +84,22 @@ class SettingsDialog(QtWidgets.QDialog): public_mode_layout.addWidget(self.public_mode_checkbox) public_mode_layout.addWidget(public_mode_label) public_mode_layout.addStretch() - public_mode_layout.setContentsMargins(0,0,0,0) + public_mode_layout.setContentsMargins(0, 0, 0, 0) self.public_mode_widget = QtWidgets.QWidget() self.public_mode_widget.setLayout(public_mode_layout) # Whether or not to use an auto-start timer self.autostart_timer_checkbox = QtWidgets.QCheckBox() self.autostart_timer_checkbox.setCheckState(QtCore.Qt.Checked) - self.autostart_timer_checkbox.setText(strings._("gui_settings_autostart_timer_checkbox")) - autostart_timer_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Start-Timer")) - autostart_timer_label.setStyleSheet(self.common.css['settings_whats_this']) + self.autostart_timer_checkbox.setText( + strings._("gui_settings_autostart_timer_checkbox") + ) + autostart_timer_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Start-Timer" + ) + ) + autostart_timer_label.setStyleSheet(self.common.css["settings_whats_this"]) autostart_timer_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) autostart_timer_label.setOpenExternalLinks(True) autostart_timer_label.setMinimumSize(public_mode_label.sizeHint()) @@ -84,16 +107,22 @@ class SettingsDialog(QtWidgets.QDialog): autostart_timer_layout.addWidget(self.autostart_timer_checkbox) autostart_timer_layout.addWidget(autostart_timer_label) autostart_timer_layout.addStretch() - autostart_timer_layout.setContentsMargins(0,0,0,0) + autostart_timer_layout.setContentsMargins(0, 0, 0, 0) self.autostart_timer_widget = QtWidgets.QWidget() self.autostart_timer_widget.setLayout(autostart_timer_layout) # Whether or not to use an auto-stop timer self.autostop_timer_checkbox = QtWidgets.QCheckBox() self.autostop_timer_checkbox.setCheckState(QtCore.Qt.Checked) - self.autostop_timer_checkbox.setText(strings._("gui_settings_autostop_timer_checkbox")) - autostop_timer_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Stop-Timer")) - autostop_timer_label.setStyleSheet(self.common.css['settings_whats_this']) + self.autostop_timer_checkbox.setText( + strings._("gui_settings_autostop_timer_checkbox") + ) + autostop_timer_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Stop-Timer" + ) + ) + autostop_timer_label.setStyleSheet(self.common.css["settings_whats_this"]) autostop_timer_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) autostop_timer_label.setOpenExternalLinks(True) autostop_timer_label.setMinimumSize(public_mode_label.sizeHint()) @@ -101,7 +130,7 @@ class SettingsDialog(QtWidgets.QDialog): autostop_timer_layout.addWidget(self.autostop_timer_checkbox) autostop_timer_layout.addWidget(autostop_timer_label) autostop_timer_layout.addStretch() - autostop_timer_layout.setContentsMargins(0,0,0,0) + autostop_timer_layout.setContentsMargins(0, 0, 0, 0) self.autostop_timer_widget = QtWidgets.QWidget() self.autostop_timer_widget.setLayout(autostop_timer_layout) @@ -116,39 +145,59 @@ class SettingsDialog(QtWidgets.QDialog): # Onion settings # Label telling user to connect to Tor for onion service settings - self.connect_to_tor_label = QtWidgets.QLabel(strings._("gui_connect_to_tor_for_onion_settings")) - self.connect_to_tor_label.setStyleSheet(self.common.css['settings_connect_to_tor']) + self.connect_to_tor_label = QtWidgets.QLabel( + strings._("gui_connect_to_tor_for_onion_settings") + ) + self.connect_to_tor_label.setStyleSheet( + self.common.css["settings_connect_to_tor"] + ) # Whether or not to save the Onion private key for reuse (persistent URL mode) self.save_private_key_checkbox = QtWidgets.QCheckBox() self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox")) - save_private_key_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-a-Persistent-URL")) - save_private_key_label.setStyleSheet(self.common.css['settings_whats_this']) + self.save_private_key_checkbox.setText( + strings._("gui_save_private_key_checkbox") + ) + save_private_key_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Using-a-Persistent-URL" + ) + ) + save_private_key_label.setStyleSheet(self.common.css["settings_whats_this"]) save_private_key_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) save_private_key_label.setOpenExternalLinks(True) save_private_key_layout = QtWidgets.QHBoxLayout() save_private_key_layout.addWidget(self.save_private_key_checkbox) save_private_key_layout.addWidget(save_private_key_label) save_private_key_layout.addStretch() - save_private_key_layout.setContentsMargins(0,0,0,0) + save_private_key_layout.setContentsMargins(0, 0, 0, 0) self.save_private_key_widget = QtWidgets.QWidget() self.save_private_key_widget.setLayout(save_private_key_layout) # Whether or not to use legacy v2 onions self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.use_legacy_v2_onions_checkbox.setText(strings._("gui_use_legacy_v2_onions_checkbox")) - self.use_legacy_v2_onions_checkbox.clicked.connect(self.use_legacy_v2_onions_checkbox_clicked) - use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Legacy-Addresses")) - use_legacy_v2_onions_label.setStyleSheet(self.common.css['settings_whats_this']) - use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + self.use_legacy_v2_onions_checkbox.setText( + strings._("gui_use_legacy_v2_onions_checkbox") + ) + self.use_legacy_v2_onions_checkbox.clicked.connect( + self.use_legacy_v2_onions_checkbox_clicked + ) + use_legacy_v2_onions_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Legacy-Addresses" + ) + ) + use_legacy_v2_onions_label.setStyleSheet(self.common.css["settings_whats_this"]) + use_legacy_v2_onions_label.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction + ) use_legacy_v2_onions_label.setOpenExternalLinks(True) use_legacy_v2_onions_layout = QtWidgets.QHBoxLayout() use_legacy_v2_onions_layout.addWidget(self.use_legacy_v2_onions_checkbox) use_legacy_v2_onions_layout.addWidget(use_legacy_v2_onions_label) use_legacy_v2_onions_layout.addStretch() - use_legacy_v2_onions_layout.setContentsMargins(0,0,0,0) + use_legacy_v2_onions_layout.setContentsMargins(0, 0, 0, 0) self.use_legacy_v2_onions_widget = QtWidgets.QWidget() self.use_legacy_v2_onions_widget.setLayout(use_legacy_v2_onions_layout) @@ -157,8 +206,12 @@ class SettingsDialog(QtWidgets.QDialog): self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setText(strings._("gui_settings_stealth_option")) self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect) - use_stealth_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services")) - use_stealth_label.setStyleSheet(self.common.css['settings_whats_this']) + use_stealth_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services" + ) + ) + use_stealth_label.setStyleSheet(self.common.css["settings_whats_this"]) use_stealth_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_stealth_label.setOpenExternalLinks(True) use_stealth_label.setMinimumSize(use_stealth_label.sizeHint()) @@ -166,17 +219,23 @@ class SettingsDialog(QtWidgets.QDialog): use_stealth_layout.addWidget(self.stealth_checkbox) use_stealth_layout.addWidget(use_stealth_label) use_stealth_layout.addStretch() - use_stealth_layout.setContentsMargins(0,0,0,0) + use_stealth_layout.setContentsMargins(0, 0, 0, 0) self.use_stealth_widget = QtWidgets.QWidget() self.use_stealth_widget.setLayout(use_stealth_layout) - self.hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string')) + self.hidservauth_details = QtWidgets.QLabel( + strings._("gui_settings_stealth_hidservauth_string") + ) self.hidservauth_details.setWordWrap(True) self.hidservauth_details.setMinimumSize(self.hidservauth_details.sizeHint()) self.hidservauth_details.hide() - self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth')) - self.hidservauth_copy_button.clicked.connect(self.hidservauth_copy_button_clicked) + self.hidservauth_copy_button = QtWidgets.QPushButton( + strings._("gui_copy_hidservauth") + ) + self.hidservauth_copy_button.clicked.connect( + self.hidservauth_copy_button_clicked + ) self.hidservauth_copy_button.hide() # Onion settings widget @@ -197,25 +256,32 @@ class SettingsDialog(QtWidgets.QDialog): onion_group = QtWidgets.QGroupBox(strings._("gui_settings_onion_label")) onion_group.setLayout(onion_group_layout) - # Sharing options # Close after first download self.close_after_first_download_checkbox = QtWidgets.QCheckBox() self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) - self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option")) + self.close_after_first_download_checkbox.setText( + strings._("gui_settings_close_after_first_download_option") + ) + individual_downloads_label = QtWidgets.QLabel( + strings._("gui_settings_individual_downloads_label") + ) # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout.addWidget(self.close_after_first_download_checkbox) + sharing_group_layout.addWidget(individual_downloads_label) sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label")) sharing_group.setLayout(sharing_group_layout) # OnionShare data dir - data_dir_label = QtWidgets.QLabel(strings._('gui_settings_data_dir_label')); + data_dir_label = QtWidgets.QLabel(strings._("gui_settings_data_dir_label")) self.data_dir_lineedit = QtWidgets.QLineEdit() self.data_dir_lineedit.setReadOnly(True) - data_dir_button = QtWidgets.QPushButton(strings._('gui_settings_data_dir_browse_button')) + data_dir_button = QtWidgets.QPushButton( + strings._("gui_settings_data_dir_browse_button") + ) data_dir_button.clicked.connect(self.data_dir_button_clicked) data_dir_layout = QtWidgets.QHBoxLayout() data_dir_layout.addWidget(data_dir_label) @@ -228,6 +294,42 @@ class SettingsDialog(QtWidgets.QDialog): receiving_group = QtWidgets.QGroupBox(strings._("gui_settings_receiving_label")) receiving_group.setLayout(receiving_group_layout) + # Option to disable Content Security Policy (for website sharing) + self.csp_header_disabled_checkbox = QtWidgets.QCheckBox() + self.csp_header_disabled_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.csp_header_disabled_checkbox.setText( + strings._("gui_settings_csp_header_disabled_option") + ) + csp_header_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Content-Security-Policy" + ) + ) + csp_header_label.setStyleSheet(self.common.css["settings_whats_this"]) + csp_header_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + csp_header_label.setOpenExternalLinks(True) + csp_header_label.setMinimumSize(csp_header_label.sizeHint()) + csp_header_layout = QtWidgets.QHBoxLayout() + csp_header_layout.addWidget(self.csp_header_disabled_checkbox) + csp_header_layout.addWidget(csp_header_label) + csp_header_layout.addStretch() + csp_header_layout.setContentsMargins(0, 0, 0, 0) + self.csp_header_widget = QtWidgets.QWidget() + self.csp_header_widget.setLayout(csp_header_layout) + + # Website settings widget + website_settings_layout = QtWidgets.QVBoxLayout() + website_settings_layout.setContentsMargins(0, 0, 0, 0) + website_settings_layout.addWidget(self.csp_header_widget) + self.website_settings_widget = QtWidgets.QWidget() + self.website_settings_widget.setLayout(website_settings_layout) + + # Website mode options layout + website_group_layout = QtWidgets.QVBoxLayout() + website_group_layout.addWidget(self.website_settings_widget) + website_group = QtWidgets.QGroupBox(strings._("gui_settings_website_label")) + website_group.setLayout(website_group_layout) + # Automatic updates options # Autoupdate @@ -239,7 +341,9 @@ class SettingsDialog(QtWidgets.QDialog): self.autoupdate_timestamp = QtWidgets.QLabel() # Check for updates button - self.check_for_updates_button = QtWidgets.QPushButton(strings._('gui_settings_autoupdate_check_button')) + self.check_for_updates_button = QtWidgets.QPushButton( + strings._("gui_settings_autoupdate_check_button") + ) self.check_for_updates_button.clicked.connect(self.check_for_updates) # We can't check for updates if not connected to Tor if not self.onion.connected_to_tor: @@ -250,18 +354,22 @@ class SettingsDialog(QtWidgets.QDialog): autoupdate_group_layout.addWidget(self.autoupdate_checkbox) autoupdate_group_layout.addWidget(self.autoupdate_timestamp) autoupdate_group_layout.addWidget(self.check_for_updates_button) - autoupdate_group = QtWidgets.QGroupBox(strings._("gui_settings_autoupdate_label")) + autoupdate_group = QtWidgets.QGroupBox( + strings._("gui_settings_autoupdate_label") + ) autoupdate_group.setLayout(autoupdate_group_layout) # Autoupdate is only available for Windows and Mac (Linux updates using package manager) - if self.system != 'Windows' and self.system != 'Darwin': + if self.system != "Windows" and self.system != "Darwin": autoupdate_group.hide() # Language settings language_label = QtWidgets.QLabel(strings._("gui_settings_language_label")) self.language_combobox = QtWidgets.QComboBox() # Populate the dropdown with all of OnionShare's available languages - language_names_to_locales = {v: k for k, v in self.common.settings.available_locales.items()} + language_names_to_locales = { + v: k for k, v in self.common.settings.available_locales.items() + } language_names = list(language_names_to_locales) language_names.sort() for language_name in language_names: @@ -275,56 +383,106 @@ class SettingsDialog(QtWidgets.QDialog): # Connection type: either automatic, control port, or socket file # Bundled Tor - self.connection_type_bundled_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_bundled_option')) - self.connection_type_bundled_radio.toggled.connect(self.connection_type_bundled_toggled) + self.connection_type_bundled_radio = QtWidgets.QRadioButton( + strings._("gui_settings_connection_type_bundled_option") + ) + self.connection_type_bundled_radio.toggled.connect( + self.connection_type_bundled_toggled + ) # Bundled Tor doesn't work on dev mode in Windows or Mac - if (self.system == 'Windows' or self.system == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False): + if (self.system == "Windows" or self.system == "Darwin") and getattr( + sys, "onionshare_dev_mode", False + ): self.connection_type_bundled_radio.setEnabled(False) # Bridge options for bundled tor # No bridges option radio - self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_no_bridges_radio_option')) - self.tor_bridges_no_bridges_radio.toggled.connect(self.tor_bridges_no_bridges_radio_toggled) + self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_no_bridges_radio_option") + ) + self.tor_bridges_no_bridges_radio.toggled.connect( + self.tor_bridges_no_bridges_radio_toggled + ) # obfs4 option radio # if the obfs4proxy binary is missing, we can't use obfs4 transports - (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths() + ( + self.tor_path, + self.tor_geo_ip_file_path, + self.tor_geo_ipv6_file_path, + self.obfs4proxy_file_path, + ) = self.common.get_tor_paths() if not os.path.isfile(self.obfs4proxy_file_path): - self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy')) + self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy") + ) self.tor_bridges_use_obfs4_radio.setEnabled(False) else: - self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option')) - self.tor_bridges_use_obfs4_radio.toggled.connect(self.tor_bridges_use_obfs4_radio_toggled) + self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_obfs4_radio_option") + ) + self.tor_bridges_use_obfs4_radio.toggled.connect( + self.tor_bridges_use_obfs4_radio_toggled + ) # meek_lite-azure option radio # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports - (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths() + ( + self.tor_path, + self.tor_geo_ip_file_path, + self.tor_geo_ipv6_file_path, + self.obfs4proxy_file_path, + ) = self.common.get_tor_paths() if not os.path.isfile(self.obfs4proxy_file_path): - self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy')) + self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( + strings._( + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy" + ) + ) self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False) else: - self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option')) - self.tor_bridges_use_meek_lite_azure_radio.toggled.connect(self.tor_bridges_use_meek_lite_azure_radio_toggled) + self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_meek_lite_azure_radio_option") + ) + self.tor_bridges_use_meek_lite_azure_radio.toggled.connect( + self.tor_bridges_use_meek_lite_azure_radio_toggled + ) # Custom bridges radio and textbox - self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_custom_radio_option')) - self.tor_bridges_use_custom_radio.toggled.connect(self.tor_bridges_use_custom_radio_toggled) - - self.tor_bridges_use_custom_label = QtWidgets.QLabel(strings._('gui_settings_tor_bridges_custom_label')) - self.tor_bridges_use_custom_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_custom_radio_option") + ) + self.tor_bridges_use_custom_radio.toggled.connect( + self.tor_bridges_use_custom_radio_toggled + ) + + self.tor_bridges_use_custom_label = QtWidgets.QLabel( + strings._("gui_settings_tor_bridges_custom_label") + ) + self.tor_bridges_use_custom_label.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction + ) self.tor_bridges_use_custom_label.setOpenExternalLinks(True) self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit() self.tor_bridges_use_custom_textbox.setMaximumHeight(200) - self.tor_bridges_use_custom_textbox.setPlaceholderText('[address:port] [identifier]') + self.tor_bridges_use_custom_textbox.setPlaceholderText( + "[address:port] [identifier]" + ) tor_bridges_use_custom_textbox_options_layout = QtWidgets.QVBoxLayout() - tor_bridges_use_custom_textbox_options_layout.addWidget(self.tor_bridges_use_custom_label) - tor_bridges_use_custom_textbox_options_layout.addWidget(self.tor_bridges_use_custom_textbox) + tor_bridges_use_custom_textbox_options_layout.addWidget( + self.tor_bridges_use_custom_label + ) + tor_bridges_use_custom_textbox_options_layout.addWidget( + self.tor_bridges_use_custom_textbox + ) self.tor_bridges_use_custom_textbox_options = QtWidgets.QWidget() - self.tor_bridges_use_custom_textbox_options.setLayout(tor_bridges_use_custom_textbox_options_layout) + self.tor_bridges_use_custom_textbox_options.setLayout( + tor_bridges_use_custom_textbox_options_layout + ) self.tor_bridges_use_custom_textbox_options.hide() # Bridges layout/widget @@ -339,41 +497,73 @@ class SettingsDialog(QtWidgets.QDialog): self.bridges.setLayout(bridges_layout) # Automatic - self.connection_type_automatic_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_automatic_option')) - self.connection_type_automatic_radio.toggled.connect(self.connection_type_automatic_toggled) + self.connection_type_automatic_radio = QtWidgets.QRadioButton( + strings._("gui_settings_connection_type_automatic_option") + ) + self.connection_type_automatic_radio.toggled.connect( + self.connection_type_automatic_toggled + ) # Control port - self.connection_type_control_port_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_control_port_option')) - self.connection_type_control_port_radio.toggled.connect(self.connection_type_control_port_toggled) - - connection_type_control_port_extras_label = QtWidgets.QLabel(strings._('gui_settings_control_port_label')) + self.connection_type_control_port_radio = QtWidgets.QRadioButton( + strings._("gui_settings_connection_type_control_port_option") + ) + self.connection_type_control_port_radio.toggled.connect( + self.connection_type_control_port_toggled + ) + + connection_type_control_port_extras_label = QtWidgets.QLabel( + strings._("gui_settings_control_port_label") + ) self.connection_type_control_port_extras_address = QtWidgets.QLineEdit() self.connection_type_control_port_extras_port = QtWidgets.QLineEdit() connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout() - connection_type_control_port_extras_layout.addWidget(connection_type_control_port_extras_label) - connection_type_control_port_extras_layout.addWidget(self.connection_type_control_port_extras_address) - connection_type_control_port_extras_layout.addWidget(self.connection_type_control_port_extras_port) + connection_type_control_port_extras_layout.addWidget( + connection_type_control_port_extras_label + ) + connection_type_control_port_extras_layout.addWidget( + self.connection_type_control_port_extras_address + ) + connection_type_control_port_extras_layout.addWidget( + self.connection_type_control_port_extras_port + ) self.connection_type_control_port_extras = QtWidgets.QWidget() - self.connection_type_control_port_extras.setLayout(connection_type_control_port_extras_layout) + self.connection_type_control_port_extras.setLayout( + connection_type_control_port_extras_layout + ) self.connection_type_control_port_extras.hide() # Socket file - self.connection_type_socket_file_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_socket_file_option')) - self.connection_type_socket_file_radio.toggled.connect(self.connection_type_socket_file_toggled) - - connection_type_socket_file_extras_label = QtWidgets.QLabel(strings._('gui_settings_socket_file_label')) + self.connection_type_socket_file_radio = QtWidgets.QRadioButton( + strings._("gui_settings_connection_type_socket_file_option") + ) + self.connection_type_socket_file_radio.toggled.connect( + self.connection_type_socket_file_toggled + ) + + connection_type_socket_file_extras_label = QtWidgets.QLabel( + strings._("gui_settings_socket_file_label") + ) self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit() connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout() - connection_type_socket_file_extras_layout.addWidget(connection_type_socket_file_extras_label) - connection_type_socket_file_extras_layout.addWidget(self.connection_type_socket_file_extras_path) + connection_type_socket_file_extras_layout.addWidget( + connection_type_socket_file_extras_label + ) + connection_type_socket_file_extras_layout.addWidget( + self.connection_type_socket_file_extras_path + ) self.connection_type_socket_file_extras = QtWidgets.QWidget() - self.connection_type_socket_file_extras.setLayout(connection_type_socket_file_extras_layout) + self.connection_type_socket_file_extras.setLayout( + connection_type_socket_file_extras_layout + ) self.connection_type_socket_file_extras.hide() # Tor SOCKS address and port - gui_settings_socks_label = QtWidgets.QLabel(strings._('gui_settings_socks_label')) + gui_settings_socks_label = QtWidgets.QLabel( + strings._("gui_settings_socks_label") + ) self.connection_type_socks_address = QtWidgets.QLineEdit() self.connection_type_socks_port = QtWidgets.QLineEdit() connection_type_socks_layout = QtWidgets.QHBoxLayout() @@ -388,18 +578,32 @@ class SettingsDialog(QtWidgets.QDialog): # Authentication options # No authentication - self.authenticate_no_auth_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_no_auth_option')) - self.authenticate_no_auth_radio.toggled.connect(self.authenticate_no_auth_toggled) + self.authenticate_no_auth_radio = QtWidgets.QRadioButton( + strings._("gui_settings_authenticate_no_auth_option") + ) + self.authenticate_no_auth_radio.toggled.connect( + self.authenticate_no_auth_toggled + ) # Password - self.authenticate_password_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_password_option')) - self.authenticate_password_radio.toggled.connect(self.authenticate_password_toggled) - - authenticate_password_extras_label = QtWidgets.QLabel(strings._('gui_settings_password_label')) - self.authenticate_password_extras_password = QtWidgets.QLineEdit('') + self.authenticate_password_radio = QtWidgets.QRadioButton( + strings._("gui_settings_authenticate_password_option") + ) + self.authenticate_password_radio.toggled.connect( + self.authenticate_password_toggled + ) + + authenticate_password_extras_label = QtWidgets.QLabel( + strings._("gui_settings_password_label") + ) + self.authenticate_password_extras_password = QtWidgets.QLineEdit("") authenticate_password_extras_layout = QtWidgets.QHBoxLayout() - authenticate_password_extras_layout.addWidget(authenticate_password_extras_label) - authenticate_password_extras_layout.addWidget(self.authenticate_password_extras_password) + authenticate_password_extras_layout.addWidget( + authenticate_password_extras_label + ) + authenticate_password_extras_layout.addWidget( + self.authenticate_password_extras_password + ) self.authenticate_password_extras = QtWidgets.QWidget() self.authenticate_password_extras.setLayout(authenticate_password_extras_layout) @@ -410,27 +614,43 @@ class SettingsDialog(QtWidgets.QDialog): authenticate_group_layout.addWidget(self.authenticate_no_auth_radio) authenticate_group_layout.addWidget(self.authenticate_password_radio) authenticate_group_layout.addWidget(self.authenticate_password_extras) - self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label")) + self.authenticate_group = QtWidgets.QGroupBox( + strings._("gui_settings_authenticate_label") + ) self.authenticate_group.setLayout(authenticate_group_layout) # Put the radios into their own group so they are exclusive connection_type_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_radio_group_layout.addWidget(self.connection_type_bundled_radio) - connection_type_radio_group_layout.addWidget(self.connection_type_automatic_radio) - connection_type_radio_group_layout.addWidget(self.connection_type_control_port_radio) - connection_type_radio_group_layout.addWidget(self.connection_type_socket_file_radio) - connection_type_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_connection_type_label")) + connection_type_radio_group_layout.addWidget( + self.connection_type_automatic_radio + ) + connection_type_radio_group_layout.addWidget( + self.connection_type_control_port_radio + ) + connection_type_radio_group_layout.addWidget( + self.connection_type_socket_file_radio + ) + connection_type_radio_group = QtWidgets.QGroupBox( + strings._("gui_settings_connection_type_label") + ) connection_type_radio_group.setLayout(connection_type_radio_group_layout) # The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges) connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_bridges_radio_group_layout.addWidget(self.bridges) - self.connection_type_bridges_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_tor_bridges")) - self.connection_type_bridges_radio_group.setLayout(connection_type_bridges_radio_group_layout) + self.connection_type_bridges_radio_group = QtWidgets.QGroupBox( + strings._("gui_settings_tor_bridges") + ) + self.connection_type_bridges_radio_group.setLayout( + connection_type_bridges_radio_group_layout + ) self.connection_type_bridges_radio_group.hide() # Test tor settings button - self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button')) + self.connection_type_test_button = QtWidgets.QPushButton( + strings._("gui_settings_connection_type_test_button") + ) self.connection_type_test_button.clicked.connect(self.test_tor_clicked) connection_type_test_button_layout = QtWidgets.QHBoxLayout() connection_type_test_button_layout.addWidget(self.connection_type_test_button) @@ -446,13 +666,15 @@ class SettingsDialog(QtWidgets.QDialog): connection_type_layout.addLayout(connection_type_test_button_layout) # Buttons - self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save')) + self.save_button = QtWidgets.QPushButton(strings._("gui_settings_button_save")) self.save_button.clicked.connect(self.save_clicked) - self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel')) + self.cancel_button = QtWidgets.QPushButton( + strings._("gui_settings_button_cancel") + ) self.cancel_button.clicked.connect(self.cancel_clicked) - version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(self.common.version)) - version_label.setStyleSheet(self.common.css['settings_version']) - self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help')) + version_label = QtWidgets.QLabel("OnionShare {0:s}".format(self.common.version)) + version_label.setStyleSheet(self.common.css["settings_version"]) + self.help_button = QtWidgets.QPushButton(strings._("gui_settings_button_help")) self.help_button.clicked.connect(self.help_clicked) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addWidget(version_label) @@ -463,7 +685,7 @@ class SettingsDialog(QtWidgets.QDialog): # Tor network connection status self.tor_status = QtWidgets.QLabel() - self.tor_status.setStyleSheet(self.common.css['settings_tor_status']) + self.tor_status.setStyleSheet(self.common.css["settings_tor_status"]) self.tor_status.hide() # Layout @@ -472,6 +694,7 @@ class SettingsDialog(QtWidgets.QDialog): left_col_layout.addWidget(onion_group) left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(receiving_group) + left_col_layout.addWidget(website_group) left_col_layout.addWidget(autoupdate_group) left_col_layout.addLayout(language_layout) left_col_layout.addStretch() @@ -484,7 +707,8 @@ class SettingsDialog(QtWidgets.QDialog): col_layout = QtWidgets.QHBoxLayout() col_layout.addLayout(left_col_layout) - col_layout.addLayout(right_col_layout) + if not self.hide_tor_settings: + col_layout.addLayout(right_col_layout) layout = QtWidgets.QVBoxLayout() layout.addLayout(col_layout) @@ -500,31 +724,37 @@ class SettingsDialog(QtWidgets.QDialog): self.old_settings = Settings(self.common, self.config) self.old_settings.load() - close_after_first_download = self.old_settings.get('close_after_first_download') + close_after_first_download = self.old_settings.get("close_after_first_download") if close_after_first_download: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) else: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Unchecked) - autostart_timer = self.old_settings.get('autostart_timer') + csp_header_disabled = self.old_settings.get("csp_header_disabled") + if csp_header_disabled: + self.csp_header_disabled_checkbox.setCheckState(QtCore.Qt.Checked) + else: + self.csp_header_disabled_checkbox.setCheckState(QtCore.Qt.Unchecked) + + autostart_timer = self.old_settings.get("autostart_timer") if autostart_timer: self.autostart_timer_checkbox.setCheckState(QtCore.Qt.Checked) else: self.autostart_timer_checkbox.setCheckState(QtCore.Qt.Unchecked) - autostop_timer = self.old_settings.get('autostop_timer') + autostop_timer = self.old_settings.get("autostop_timer") if autostop_timer: self.autostop_timer_checkbox.setCheckState(QtCore.Qt.Checked) else: self.autostop_timer_checkbox.setCheckState(QtCore.Qt.Unchecked) - save_private_key = self.old_settings.get('save_private_key') + save_private_key = self.old_settings.get("save_private_key") if save_private_key: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Checked) else: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) - use_legacy_v2_onions = self.old_settings.get('use_legacy_v2_onions') + use_legacy_v2_onions = self.old_settings.get("use_legacy_v2_onions") if use_legacy_v2_onions: self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) @@ -532,84 +762,102 @@ class SettingsDialog(QtWidgets.QDialog): else: self.use_stealth_widget.hide() - data_dir = self.old_settings.get('data_dir') + data_dir = self.old_settings.get("data_dir") self.data_dir_lineedit.setText(data_dir) - public_mode = self.old_settings.get('public_mode') + public_mode = self.old_settings.get("public_mode") if public_mode: self.public_mode_checkbox.setCheckState(QtCore.Qt.Checked) else: self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) - use_stealth = self.old_settings.get('use_stealth') + use_stealth = self.old_settings.get("use_stealth") if use_stealth: self.stealth_checkbox.setCheckState(QtCore.Qt.Checked) # Legacy v2 mode is forced on if Stealth is enabled self.use_legacy_v2_onions_checkbox.setEnabled(False) - if save_private_key and self.old_settings.get('hidservauth_string') != "": + if save_private_key and self.old_settings.get("hidservauth_string") != "": self.hidservauth_details.show() self.hidservauth_copy_button.show() else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) - use_autoupdate = self.old_settings.get('use_autoupdate') + use_autoupdate = self.old_settings.get("use_autoupdate") if use_autoupdate: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Checked) else: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) - autoupdate_timestamp = self.old_settings.get('autoupdate_timestamp') + autoupdate_timestamp = self.old_settings.get("autoupdate_timestamp") self._update_autoupdate_timestamp(autoupdate_timestamp) - locale = self.old_settings.get('locale') + locale = self.old_settings.get("locale") locale_index = self.language_combobox.findData(QtCore.QVariant(locale)) self.language_combobox.setCurrentIndex(locale_index) - connection_type = self.old_settings.get('connection_type') - if connection_type == 'bundled': + connection_type = self.old_settings.get("connection_type") + if connection_type == "bundled": if self.connection_type_bundled_radio.isEnabled(): self.connection_type_bundled_radio.setChecked(True) else: # If bundled tor is disabled, fallback to automatic self.connection_type_automatic_radio.setChecked(True) - elif connection_type == 'automatic': + elif connection_type == "automatic": self.connection_type_automatic_radio.setChecked(True) - elif connection_type == 'control_port': + elif connection_type == "control_port": self.connection_type_control_port_radio.setChecked(True) - elif connection_type == 'socket_file': + elif connection_type == "socket_file": self.connection_type_socket_file_radio.setChecked(True) - self.connection_type_control_port_extras_address.setText(self.old_settings.get('control_port_address')) - self.connection_type_control_port_extras_port.setText(str(self.old_settings.get('control_port_port'))) - self.connection_type_socket_file_extras_path.setText(self.old_settings.get('socket_file_path')) - self.connection_type_socks_address.setText(self.old_settings.get('socks_address')) - self.connection_type_socks_port.setText(str(self.old_settings.get('socks_port'))) - auth_type = self.old_settings.get('auth_type') - if auth_type == 'no_auth': + self.connection_type_control_port_extras_address.setText( + self.old_settings.get("control_port_address") + ) + self.connection_type_control_port_extras_port.setText( + str(self.old_settings.get("control_port_port")) + ) + self.connection_type_socket_file_extras_path.setText( + self.old_settings.get("socket_file_path") + ) + self.connection_type_socks_address.setText( + self.old_settings.get("socks_address") + ) + self.connection_type_socks_port.setText( + str(self.old_settings.get("socks_port")) + ) + auth_type = self.old_settings.get("auth_type") + if auth_type == "no_auth": self.authenticate_no_auth_radio.setChecked(True) - elif auth_type == 'password': + elif auth_type == "password": self.authenticate_password_radio.setChecked(True) - self.authenticate_password_extras_password.setText(self.old_settings.get('auth_password')) + self.authenticate_password_extras_password.setText( + self.old_settings.get("auth_password") + ) - if self.old_settings.get('no_bridges'): + if self.old_settings.get("no_bridges"): self.tor_bridges_no_bridges_radio.setChecked(True) self.tor_bridges_use_obfs4_radio.setChecked(False) self.tor_bridges_use_meek_lite_azure_radio.setChecked(False) self.tor_bridges_use_custom_radio.setChecked(False) else: self.tor_bridges_no_bridges_radio.setChecked(False) - self.tor_bridges_use_obfs4_radio.setChecked(self.old_settings.get('tor_bridges_use_obfs4')) - self.tor_bridges_use_meek_lite_azure_radio.setChecked(self.old_settings.get('tor_bridges_use_meek_lite_azure')) - - if self.old_settings.get('tor_bridges_use_custom_bridges'): + self.tor_bridges_use_obfs4_radio.setChecked( + self.old_settings.get("tor_bridges_use_obfs4") + ) + self.tor_bridges_use_meek_lite_azure_radio.setChecked( + self.old_settings.get("tor_bridges_use_meek_lite_azure") + ) + + if self.old_settings.get("tor_bridges_use_custom_bridges"): self.tor_bridges_use_custom_radio.setChecked(True) # Remove the 'Bridge' lines at the start of each bridge. # They are added automatically to provide compatibility with # copying/pasting bridges provided from https://bridges.torproject.org new_bridges = [] - bridges = self.old_settings.get('tor_bridges_use_custom_bridges').split('Bridge ') + bridges = self.old_settings.get("tor_bridges_use_custom_bridges").split( + "Bridge " + ) for bridge in bridges: new_bridges.append(bridge) - new_bridges = ''.join(new_bridges) + new_bridges = "".join(new_bridges) self.tor_bridges_use_custom_textbox.setPlainText(new_bridges) # If we're connected to Tor, show onion service settings, show label if not @@ -619,22 +867,25 @@ class SettingsDialog(QtWidgets.QDialog): # If v3 onion services are supported, allow using legacy mode if self.onion.supports_v3_onions: - self.common.log('SettingsDialog', '__init__', 'v3 onions are supported') + self.common.log("SettingsDialog", "__init__", "v3 onions are supported") self.use_legacy_v2_onions_checkbox.show() else: - self.common.log('SettingsDialog', '__init__', 'v3 onions are not supported') + self.common.log( + "SettingsDialog", "__init__", "v3 onions are not supported" + ) self.use_legacy_v2_onions_widget.hide() self.use_legacy_v2_onions_checkbox_clicked(True) else: self.connect_to_tor_label.show() self.onion_settings_widget.hide() - def connection_type_bundled_toggled(self, checked): """ Connection type bundled was toggled. If checked, hide authentication fields. """ - self.common.log('SettingsDialog', 'connection_type_bundled_toggled') + self.common.log("SettingsDialog", "connection_type_bundled_toggled") + if self.hide_tor_settings: + return if checked: self.authenticate_group.hide() self.connection_type_socks.hide() @@ -644,6 +895,8 @@ class SettingsDialog(QtWidgets.QDialog): """ 'No bridges' option was toggled. If checked, enable other bridge options. """ + if self.hide_tor_settings: + return if checked: self.tor_bridges_use_custom_textbox_options.hide() @@ -651,6 +904,8 @@ class SettingsDialog(QtWidgets.QDialog): """ obfs4 bridges option was toggled. If checked, disable custom bridge options. """ + if self.hide_tor_settings: + return if checked: self.tor_bridges_use_custom_textbox_options.hide() @@ -658,16 +913,24 @@ class SettingsDialog(QtWidgets.QDialog): """ meek_lite_azure bridges option was toggled. If checked, disable custom bridge options. """ + if self.hide_tor_settings: + return if checked: self.tor_bridges_use_custom_textbox_options.hide() # Alert the user about meek's costliness if it looks like they're turning it on - if not self.old_settings.get('tor_bridges_use_meek_lite_azure'): - Alert(self.common, strings._('gui_settings_meek_lite_expensive_warning'), QtWidgets.QMessageBox.Warning) + if not self.old_settings.get("tor_bridges_use_meek_lite_azure"): + Alert( + self.common, + strings._("gui_settings_meek_lite_expensive_warning"), + QtWidgets.QMessageBox.Warning, + ) def tor_bridges_use_custom_radio_toggled(self, checked): """ Custom bridges option was toggled. If checked, show custom bridge options. """ + if self.hide_tor_settings: + return if checked: self.tor_bridges_use_custom_textbox_options.show() @@ -675,7 +938,9 @@ class SettingsDialog(QtWidgets.QDialog): """ Connection type automatic was toggled. If checked, hide authentication fields. """ - self.common.log('SettingsDialog', 'connection_type_automatic_toggled') + self.common.log("SettingsDialog", "connection_type_automatic_toggled") + if self.hide_tor_settings: + return if checked: self.authenticate_group.hide() self.connection_type_socks.hide() @@ -686,7 +951,9 @@ class SettingsDialog(QtWidgets.QDialog): Connection type control port was toggled. If checked, show extra fields for Tor control address and port. If unchecked, hide those extra fields. """ - self.common.log('SettingsDialog', 'connection_type_control_port_toggled') + self.common.log("SettingsDialog", "connection_type_control_port_toggled") + if self.hide_tor_settings: + return if checked: self.authenticate_group.show() self.connection_type_control_port_extras.show() @@ -695,13 +962,14 @@ class SettingsDialog(QtWidgets.QDialog): else: self.connection_type_control_port_extras.hide() - def connection_type_socket_file_toggled(self, checked): """ Connection type socket file was toggled. If checked, show extra fields for socket file. If unchecked, hide those extra fields. """ - self.common.log('SettingsDialog', 'connection_type_socket_file_toggled') + self.common.log("SettingsDialog", "connection_type_socket_file_toggled") + if self.hide_tor_settings: + return if checked: self.authenticate_group.show() self.connection_type_socket_file_extras.show() @@ -714,14 +982,14 @@ class SettingsDialog(QtWidgets.QDialog): """ Authentication option no authentication was toggled. """ - self.common.log('SettingsDialog', 'authenticate_no_auth_toggled') + self.common.log("SettingsDialog", "authenticate_no_auth_toggled") def authenticate_password_toggled(self, checked): """ Authentication option password was toggled. If checked, show extra fields for password auth. If unchecked, hide those extra fields. """ - self.common.log('SettingsDialog', 'authenticate_password_toggled') + self.common.log("SettingsDialog", "authenticate_password_toggled") if checked: self.authenticate_password_extras.show() else: @@ -732,9 +1000,13 @@ class SettingsDialog(QtWidgets.QDialog): Toggle the 'Copy HidServAuth' button to copy the saved HidServAuth to clipboard. """ - self.common.log('SettingsDialog', 'hidservauth_copy_button_clicked', 'HidServAuth was copied to clipboard') + self.common.log( + "SettingsDialog", + "hidservauth_copy_button_clicked", + "HidServAuth was copied to clipboard", + ) clipboard = self.qtapp.clipboard() - clipboard.setText(self.old_settings.get('hidservauth_string')) + clipboard.setText(self.old_settings.get("hidservauth_string")) def use_legacy_v2_onions_checkbox_clicked(self, checked): """ @@ -760,11 +1032,16 @@ class SettingsDialog(QtWidgets.QDialog): Browse for a new OnionShare data directory """ data_dir = self.data_dir_lineedit.text() - selected_dir = QtWidgets.QFileDialog.getExistingDirectory(self, - strings._('gui_settings_data_dir_label'), data_dir) + selected_dir = QtWidgets.QFileDialog.getExistingDirectory( + self, strings._("gui_settings_data_dir_label"), data_dir + ) if selected_dir: - self.common.log('SettingsDialog', 'data_dir_button_clicked', 'selected dir: {}'.format(selected_dir)) + self.common.log( + "SettingsDialog", + "data_dir_button_clicked", + "selected dir: {}".format(selected_dir), + ) self.data_dir_lineedit.setText(selected_dir) def test_tor_clicked(self): @@ -772,33 +1049,57 @@ class SettingsDialog(QtWidgets.QDialog): Test Tor Settings button clicked. With the given settings, see if we can successfully connect and authenticate to Tor. """ - self.common.log('SettingsDialog', 'test_tor_clicked') + self.common.log("SettingsDialog", "test_tor_clicked") settings = self.settings_from_fields() try: # Show Tor connection status if connection type is bundled tor - if settings.get('connection_type') == 'bundled': + if settings.get("connection_type") == "bundled": self.tor_status.show() self._disable_buttons() def tor_status_update_func(progress, summary): self._tor_status_update(progress, summary) return True + else: tor_status_update_func = None onion = Onion(self.common) - onion.connect(custom_settings=settings, config=self.config, tor_status_update_func=tor_status_update_func) + onion.connect( + custom_settings=settings, + config=self.config, + tor_status_update_func=tor_status_update_func, + ) # If an exception hasn't been raised yet, the Tor settings work - Alert(self.common, strings._('settings_test_success').format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth, onion.supports_v3_onions)) + Alert( + self.common, + strings._("settings_test_success").format( + onion.tor_version, + onion.supports_ephemeral, + onion.supports_stealth, + onion.supports_v3_onions, + ), + ) # Clean up onion.cleanup() - except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e: + except ( + TorErrorInvalidSetting, + TorErrorAutomatic, + TorErrorSocketPort, + TorErrorSocketFile, + TorErrorMissingPassword, + TorErrorUnreadableCookieFile, + TorErrorAuthError, + TorErrorProtocolError, + BundledTorNotSupported, + BundledTorTimeout, + ) as e: Alert(self.common, e.args[0], QtWidgets.QMessageBox.Warning) - if settings.get('connection_type') == 'bundled': + if settings.get("connection_type") == "bundled": self.tor_status.hide() self._enable_buttons() @@ -806,7 +1107,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Check for Updates button clicked. Manually force an update check. """ - self.common.log('SettingsDialog', 'check_for_updates') + self.common.log("SettingsDialog", "check_for_updates") # Disable buttons self._disable_buttons() self.qtapp.processEvents() @@ -815,7 +1116,7 @@ class SettingsDialog(QtWidgets.QDialog): # Update the last checked label settings = Settings(self.common, self.config) settings.load() - autoupdate_timestamp = settings.get('autoupdate_timestamp') + autoupdate_timestamp = settings.get("autoupdate_timestamp") self._update_autoupdate_timestamp(autoupdate_timestamp) def close_forced_update_thread(): @@ -827,22 +1128,37 @@ class SettingsDialog(QtWidgets.QDialog): # Check for updates def update_available(update_url, installed_version, latest_version): - Alert(self.common, strings._("update_available").format(update_url, installed_version, latest_version)) + Alert( + self.common, + strings._("update_available").format( + update_url, installed_version, latest_version + ), + ) close_forced_update_thread() def update_not_available(): - Alert(self.common, strings._('update_not_available')) + Alert(self.common, strings._("update_not_available")) close_forced_update_thread() def update_error(): - Alert(self.common, strings._('update_error_check_error'), QtWidgets.QMessageBox.Warning) + Alert( + self.common, + strings._("update_error_check_error"), + QtWidgets.QMessageBox.Warning, + ) close_forced_update_thread() def update_invalid_version(latest_version): - Alert(self.common, strings._('update_error_invalid_latest_version').format(latest_version), QtWidgets.QMessageBox.Warning) + Alert( + self.common, + strings._("update_error_invalid_latest_version").format(latest_version), + QtWidgets.QMessageBox.Warning, + ) close_forced_update_thread() - forced_update_thread = UpdateThread(self.common, self.onion, self.config, force=True) + forced_update_thread = UpdateThread( + self.common, self.onion, self.config, force=True + ) forced_update_thread.update_available.connect(update_available) forced_update_thread.update_not_available.connect(update_not_available) forced_update_thread.update_error.connect(update_error) @@ -853,7 +1169,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Save button clicked. Save current settings to disk. """ - self.common.log('SettingsDialog', 'save_clicked') + self.common.log("SettingsDialog", "save_clicked") def changed(s1, s2, keys): """ @@ -868,13 +1184,19 @@ class SettingsDialog(QtWidgets.QDialog): settings = self.settings_from_fields() if settings: # If language changed, inform user they need to restart OnionShare - if changed(settings, self.old_settings, ['locale']): + if changed(settings, self.old_settings, ["locale"]): # Look up error message in different locale - new_locale = settings.get('locale') - if new_locale in strings.translations and 'gui_settings_language_changed_notice' in strings.translations[new_locale]: - notice = strings.translations[new_locale]['gui_settings_language_changed_notice'] + new_locale = settings.get("locale") + if ( + new_locale in strings.translations + and "gui_settings_language_changed_notice" + in strings.translations[new_locale] + ): + notice = strings.translations[new_locale][ + "gui_settings_language_changed_notice" + ] else: - notice = strings._('gui_settings_language_changed_notice') + notice = strings._("gui_settings_language_changed_notice") Alert(self.common, notice, QtWidgets.QMessageBox.Information) # Save the new settings @@ -885,33 +1207,58 @@ class SettingsDialog(QtWidgets.QDialog): reboot_onion = False if not self.local_only: if self.onion.is_authenticated(): - self.common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') - - if changed(settings, self.old_settings, [ - 'connection_type', 'control_port_address', - 'control_port_port', 'socks_address', 'socks_port', - 'socket_file_path', 'auth_type', 'auth_password', - 'no_bridges', 'tor_bridges_use_obfs4', - 'tor_bridges_use_meek_lite_azure', - 'tor_bridges_use_custom_bridges']): + self.common.log( + "SettingsDialog", "save_clicked", "Connected to Tor" + ) + + if changed( + settings, + self.old_settings, + [ + "connection_type", + "control_port_address", + "control_port_port", + "socks_address", + "socks_port", + "socket_file_path", + "auth_type", + "auth_password", + "no_bridges", + "tor_bridges_use_obfs4", + "tor_bridges_use_meek_lite_azure", + "tor_bridges_use_custom_bridges", + ], + ): reboot_onion = True else: - self.common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor') + self.common.log( + "SettingsDialog", "save_clicked", "Not connected to Tor" + ) # Tor isn't connected, so try connecting reboot_onion = True # Do we need to reinitialize Tor? if reboot_onion: # Reinitialize the Onion object - self.common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') + self.common.log( + "SettingsDialog", "save_clicked", "rebooting the Onion" + ) self.onion.cleanup() - tor_con = TorConnectionDialog(self.common, self.qtapp, self.onion, settings) + tor_con = TorConnectionDialog( + self.common, self.qtapp, self.onion, settings + ) tor_con.start() - self.common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) + self.common.log( + "SettingsDialog", + "save_clicked", + "Onion done rebooting, connected to Tor: {}".format( + self.onion.connected_to_tor + ), + ) if self.onion.is_authenticated() and not tor_con.wasCanceled(): self.settings_saved.emit() @@ -928,9 +1275,13 @@ class SettingsDialog(QtWidgets.QDialog): """ Cancel button clicked. """ - self.common.log('SettingsDialog', 'cancel_clicked') + self.common.log("SettingsDialog", "cancel_clicked") if not self.local_only and not self.onion.is_authenticated(): - Alert(self.common, strings._('gui_tor_connection_canceled'), QtWidgets.QMessageBox.Warning) + Alert( + self.common, + strings._("gui_tor_connection_canceled"), + QtWidgets.QMessageBox.Warning, + ) sys.exit() else: self.close() @@ -939,25 +1290,31 @@ class SettingsDialog(QtWidgets.QDialog): """ Help button clicked. """ - self.common.log('SettingsDialog', 'help_clicked') + self.common.log("SettingsDialog", "help_clicked") SettingsDialog.open_help() @staticmethod def open_help(): - help_url = 'https://github.com/micahflee/onionshare/wiki' + help_url = "https://github.com/micahflee/onionshare/wiki" QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_url)) def settings_from_fields(self): """ Return a Settings object that's full of values from the settings dialog. """ - self.common.log('SettingsDialog', 'settings_from_fields') + self.common.log("SettingsDialog", "settings_from_fields") settings = Settings(self.common, self.config) - settings.load() # To get the last update timestamp - - settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked()) - settings.set('autostart_timer', self.autostart_timer_checkbox.isChecked()) - settings.set('autostop_timer', self.autostop_timer_checkbox.isChecked()) + settings.load() # To get the last update timestamp + + settings.set( + "close_after_first_download", + self.close_after_first_download_checkbox.isChecked(), + ) + settings.set( + "csp_header_disabled", self.csp_header_disabled_checkbox.isChecked() + ) + settings.set("autostart_timer", self.autostart_timer_checkbox.isChecked()) + settings.set("autostop_timer", self.autostop_timer_checkbox.isChecked()) # Complicated logic here to force v2 onion mode on or off depending on other settings if self.use_legacy_v2_onions_checkbox.isChecked(): @@ -966,142 +1323,167 @@ class SettingsDialog(QtWidgets.QDialog): use_legacy_v2_onions = False if self.save_private_key_checkbox.isChecked(): - settings.set('save_private_key', True) - settings.set('private_key', self.old_settings.get('private_key')) - settings.set('slug', self.old_settings.get('slug')) - settings.set('hidservauth_string', self.old_settings.get('hidservauth_string')) + settings.set("save_private_key", True) + settings.set("private_key", self.old_settings.get("private_key")) + settings.set("password", self.old_settings.get("password")) + settings.set( + "hidservauth_string", self.old_settings.get("hidservauth_string") + ) else: - settings.set('save_private_key', False) - settings.set('private_key', '') - settings.set('slug', '') + settings.set("save_private_key", False) + settings.set("private_key", "") + settings.set("password", "") # Also unset the HidServAuth if we are removing our reusable private key - settings.set('hidservauth_string', '') + settings.set("hidservauth_string", "") if use_legacy_v2_onions: - settings.set('use_legacy_v2_onions', True) + settings.set("use_legacy_v2_onions", True) else: - settings.set('use_legacy_v2_onions', False) + settings.set("use_legacy_v2_onions", False) - settings.set('data_dir', self.data_dir_lineedit.text()) - settings.set('public_mode', self.public_mode_checkbox.isChecked()) - settings.set('use_stealth', self.stealth_checkbox.isChecked()) + settings.set("data_dir", self.data_dir_lineedit.text()) + settings.set("public_mode", self.public_mode_checkbox.isChecked()) + settings.set("use_stealth", self.stealth_checkbox.isChecked()) # Always unset the HidServAuth if Stealth mode is unset if not self.stealth_checkbox.isChecked(): - settings.set('hidservauth_string', '') + settings.set("hidservauth_string", "") # Language locale_index = self.language_combobox.currentIndex() locale = self.language_combobox.itemData(locale_index) - settings.set('locale', locale) + settings.set("locale", locale) # Tor connection if self.connection_type_bundled_radio.isChecked(): - settings.set('connection_type', 'bundled') + settings.set("connection_type", "bundled") if self.connection_type_automatic_radio.isChecked(): - settings.set('connection_type', 'automatic') + settings.set("connection_type", "automatic") if self.connection_type_control_port_radio.isChecked(): - settings.set('connection_type', 'control_port') + settings.set("connection_type", "control_port") if self.connection_type_socket_file_radio.isChecked(): - settings.set('connection_type', 'socket_file') + settings.set("connection_type", "socket_file") if self.autoupdate_checkbox.isChecked(): - settings.set('use_autoupdate', True) + settings.set("use_autoupdate", True) else: - settings.set('use_autoupdate', False) - - settings.set('control_port_address', self.connection_type_control_port_extras_address.text()) - settings.set('control_port_port', self.connection_type_control_port_extras_port.text()) - settings.set('socket_file_path', self.connection_type_socket_file_extras_path.text()) - - settings.set('socks_address', self.connection_type_socks_address.text()) - settings.set('socks_port', self.connection_type_socks_port.text()) + settings.set("use_autoupdate", False) + + settings.set( + "control_port_address", + self.connection_type_control_port_extras_address.text(), + ) + settings.set( + "control_port_port", self.connection_type_control_port_extras_port.text() + ) + settings.set( + "socket_file_path", self.connection_type_socket_file_extras_path.text() + ) + + settings.set("socks_address", self.connection_type_socks_address.text()) + settings.set("socks_port", self.connection_type_socks_port.text()) if self.authenticate_no_auth_radio.isChecked(): - settings.set('auth_type', 'no_auth') + settings.set("auth_type", "no_auth") if self.authenticate_password_radio.isChecked(): - settings.set('auth_type', 'password') + settings.set("auth_type", "password") - settings.set('auth_password', self.authenticate_password_extras_password.text()) + settings.set("auth_password", self.authenticate_password_extras_password.text()) # Whether we use bridges if self.tor_bridges_no_bridges_radio.isChecked(): - settings.set('no_bridges', True) - settings.set('tor_bridges_use_obfs4', False) - settings.set('tor_bridges_use_meek_lite_azure', False) - settings.set('tor_bridges_use_custom_bridges', '') + settings.set("no_bridges", True) + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_obfs4_radio.isChecked(): - settings.set('no_bridges', False) - settings.set('tor_bridges_use_obfs4', True) - settings.set('tor_bridges_use_meek_lite_azure', False) - settings.set('tor_bridges_use_custom_bridges', '') + settings.set("no_bridges", False) + settings.set("tor_bridges_use_obfs4", True) + settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_meek_lite_azure_radio.isChecked(): - settings.set('no_bridges', False) - settings.set('tor_bridges_use_obfs4', False) - settings.set('tor_bridges_use_meek_lite_azure', True) - settings.set('tor_bridges_use_custom_bridges', '') + settings.set("no_bridges", False) + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", True) + settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_custom_radio.isChecked(): - settings.set('no_bridges', False) - settings.set('tor_bridges_use_obfs4', False) - settings.set('tor_bridges_use_meek_lite_azure', False) + settings.set("no_bridges", False) + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", False) # Insert a 'Bridge' line at the start of each bridge. # This makes it easier to copy/paste a set of bridges # provided from https://bridges.torproject.org new_bridges = [] - bridges = self.tor_bridges_use_custom_textbox.toPlainText().split('\n') + bridges = self.tor_bridges_use_custom_textbox.toPlainText().split("\n") bridges_valid = False for bridge in bridges: - if bridge != '': + if bridge != "": # Check the syntax of the custom bridge to make sure it looks legitimate - ipv4_pattern = re.compile("(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$") - ipv6_pattern = re.compile("(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$") - meek_lite_pattern = re.compile("(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)") - if ipv4_pattern.match(bridge) or \ - ipv6_pattern.match(bridge) or \ - meek_lite_pattern.match(bridge): - new_bridges.append(''.join(['Bridge ', bridge, '\n'])) + ipv4_pattern = re.compile( + "(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$" + ) + ipv6_pattern = re.compile( + "(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$" + ) + meek_lite_pattern = re.compile( + "(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)" + ) + if ( + ipv4_pattern.match(bridge) + or ipv6_pattern.match(bridge) + or meek_lite_pattern.match(bridge) + ): + new_bridges.append("".join(["Bridge ", bridge, "\n"])) bridges_valid = True if bridges_valid: - new_bridges = ''.join(new_bridges) - settings.set('tor_bridges_use_custom_bridges', new_bridges) + new_bridges = "".join(new_bridges) + settings.set("tor_bridges_use_custom_bridges", new_bridges) else: - Alert(self.common, strings._('gui_settings_tor_bridges_invalid')) - settings.set('no_bridges', True) + Alert(self.common, strings._("gui_settings_tor_bridges_invalid")) + settings.set("no_bridges", True) return False return settings def closeEvent(self, e): - self.common.log('SettingsDialog', 'closeEvent') + self.common.log("SettingsDialog", "closeEvent") # On close, if Tor isn't connected, then quit OnionShare altogether if not self.local_only: if not self.onion.is_authenticated(): - self.common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor') + self.common.log( + "SettingsDialog", "closeEvent", "Closing while not connected to Tor" + ) # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.qtapp.quit) def _update_autoupdate_timestamp(self, autoupdate_timestamp): - self.common.log('SettingsDialog', '_update_autoupdate_timestamp') + self.common.log("SettingsDialog", "_update_autoupdate_timestamp") if autoupdate_timestamp: dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) - last_checked = dt.strftime('%B %d, %Y %H:%M') + last_checked = dt.strftime("%B %d, %Y %H:%M") else: - last_checked = strings._('gui_settings_autoupdate_timestamp_never') - self.autoupdate_timestamp.setText(strings._('gui_settings_autoupdate_timestamp').format(last_checked)) + last_checked = strings._("gui_settings_autoupdate_timestamp_never") + self.autoupdate_timestamp.setText( + strings._("gui_settings_autoupdate_timestamp").format(last_checked) + ) def _tor_status_update(self, progress, summary): - self.tor_status.setText('<strong>{}</strong><br>{}% {}'.format(strings._('connecting_to_tor'), progress, summary)) + self.tor_status.setText( + "<strong>{}</strong><br>{}% {}".format( + strings._("connecting_to_tor"), progress, summary + ) + ) self.qtapp.processEvents() - if 'Done' in summary: + if "Done" in summary: self.tor_status.hide() self._enable_buttons() def _disable_buttons(self): - self.common.log('SettingsDialog', '_disable_buttons') + self.common.log("SettingsDialog", "_disable_buttons") self.check_for_updates_button.setEnabled(False) self.connection_type_test_button.setEnabled(False) @@ -1109,7 +1491,7 @@ class SettingsDialog(QtWidgets.QDialog): self.cancel_button.setEnabled(False) def _enable_buttons(self): - self.common.log('SettingsDialog', '_enable_buttons') + self.common.log("SettingsDialog", "_enable_buttons") # We can't check for updates if we're still not connected to Tor if not self.onion.connected_to_tor: self.check_for_updates_button.setEnabled(False) diff --git a/onionshare_gui/threads.py b/onionshare_gui/threads.py index 26a9ee6b..090574c1 100644 --- a/onionshare_gui/threads.py +++ b/onionshare_gui/threads.py @@ -27,6 +27,7 @@ class OnionThread(QtCore.QThread): """ Starts the onion service, and waits for it to finish """ + success = QtCore.pyqtSignal() success_early = QtCore.pyqtSignal() error = QtCore.pyqtSignal(str) @@ -34,25 +35,34 @@ class OnionThread(QtCore.QThread): def __init__(self, mode): super(OnionThread, self).__init__() self.mode = mode - self.mode.common.log('OnionThread', '__init__') + self.mode.common.log("OnionThread", "__init__") # allow this thread to be terminated self.setTerminationEnabled() def run(self): - self.mode.common.log('OnionThread', 'run') + self.mode.common.log("OnionThread", "run") + + # Make a new static URL path for each new share + self.mode.web.generate_static_url_path() - # Choose port and slug early, because we need them to exist in advance for scheduled shares - self.mode.app.stay_open = not self.mode.common.settings.get('close_after_first_download') + # Choose port and password early, because we need them to exist in advance for scheduled shares + self.mode.app.stay_open = not self.mode.common.settings.get( + "close_after_first_download" + ) if not self.mode.app.port: self.mode.app.choose_port() - if not self.mode.common.settings.get('public_mode'): - if not self.mode.web.slug: - self.mode.web.generate_slug(self.mode.common.settings.get('slug')) + if not self.mode.common.settings.get("public_mode"): + if not self.mode.web.password: + self.mode.web.generate_password( + self.mode.common.settings.get("password") + ) try: if self.mode.obtain_onion_early: - self.mode.app.start_onion_service(await_publication=False, save_scheduled_key=True) + self.mode.app.start_onion_service( + await_publication=False, save_scheduled_key=True + ) # wait for modules in thread to load, preventing a thread-related cx_Freeze crash time.sleep(0.2) self.success_early.emit() @@ -67,7 +77,19 @@ class OnionThread(QtCore.QThread): self.mode.web_thread.start() self.success.emit() - except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e: + except ( + TorTooOld, + TorErrorInvalidSetting, + TorErrorAutomatic, + TorErrorSocketPort, + TorErrorSocketFile, + TorErrorMissingPassword, + TorErrorUnreadableCookieFile, + TorErrorAuthError, + TorErrorProtocolError, + BundledTorTimeout, + OSError, + ) as e: self.error.emit(e.args[0]) return @@ -76,17 +98,23 @@ class WebThread(QtCore.QThread): """ Starts the web service """ + success = QtCore.pyqtSignal() error = QtCore.pyqtSignal(str) def __init__(self, mode): super(WebThread, self).__init__() self.mode = mode - self.mode.common.log('WebThread', '__init__') + self.mode.common.log("WebThread", "__init__") def run(self): - self.mode.common.log('WebThread', 'run') - self.mode.web.start(self.mode.app.port, self.mode.app.stay_open, self.mode.common.settings.get('public_mode'), self.mode.web.slug) + self.mode.common.log("WebThread", "run") + self.mode.web.start( + self.mode.app.port, + self.mode.app.stay_open, + self.mode.common.settings.get("public_mode"), + self.mode.web.password, + ) self.success.emit() @@ -94,30 +122,40 @@ class AutoStartTimer(QtCore.QThread): """ Waits for a prescribed time before allowing a share to start """ + success = QtCore.pyqtSignal() error = QtCore.pyqtSignal(str) + def __init__(self, mode, canceled=False): super(AutoStartTimer, self).__init__() self.mode = mode self.canceled = canceled - self.mode.common.log('AutoStartTimer', '__init__') + self.mode.common.log("AutoStartTimer", "__init__") # allow this thread to be terminated self.setTerminationEnabled() def run(self): now = QtCore.QDateTime.currentDateTime() - autostart_timer_datetime_delta = now.secsTo(self.mode.server_status.autostart_timer_datetime) + autostart_timer_datetime_delta = now.secsTo( + self.mode.server_status.autostart_timer_datetime + ) try: # Sleep until scheduled time while autostart_timer_datetime_delta > 0 and self.canceled == False: time.sleep(0.1) now = QtCore.QDateTime.currentDateTime() - autostart_timer_datetime_delta = now.secsTo(self.mode.server_status.autostart_timer_datetime) + autostart_timer_datetime_delta = now.secsTo( + self.mode.server_status.autostart_timer_datetime + ) # Timer has now finished if self.canceled == False: - self.mode.server_status.server_button.setText(strings._('gui_please_wait')) - self.mode.server_status_label.setText(strings._('gui_status_indicator_share_working')) + self.mode.server_status.server_button.setText( + strings._("gui_please_wait") + ) + self.mode.server_status_label.setText( + strings._("gui_status_indicator_share_working") + ) self.success.emit() except ValueError as e: self.error.emit(e.args[0]) diff --git a/onionshare_gui/tor_connection_dialog.py b/onionshare_gui/tor_connection_dialog.py index 2bcbf1a6..58fc01d0 100644 --- a/onionshare_gui/tor_connection_dialog.py +++ b/onionshare_gui/tor_connection_dialog.py @@ -24,10 +24,12 @@ from onionshare.onion import * from .widgets import Alert + class TorConnectionDialog(QtWidgets.QProgressDialog): """ Connecting to Tor dialog. """ + open_settings = QtCore.pyqtSignal() def __init__(self, common, qtapp, onion, custom_settings=False): @@ -40,18 +42,20 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): else: self.settings = self.common.settings - self.common.log('TorConnectionDialog', '__init__') + self.common.log("TorConnectionDialog", "__init__") self.qtapp = qtapp self.onion = onion self.setWindowTitle("OnionShare") - self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) + self.setWindowIcon( + QtGui.QIcon(self.common.get_resource_path("images/logo.png")) + ) self.setModal(True) self.setFixedSize(400, 150) # Label - self.setLabelText(strings._('connecting_to_tor')) + self.setLabelText(strings._("connecting_to_tor")) # Progress bar ticks from 0 to 100 self.setRange(0, 100) @@ -59,10 +63,10 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): self.setMinimumDuration(100) # Start displaying the status at 0 - self._tor_status_update(0, '') + self._tor_status_update(0, "") def start(self): - self.common.log('TorConnectionDialog', 'start') + self.common.log("TorConnectionDialog", "start") t = TorConnectionThread(self.common, self.settings, self, self.onion) t.tor_status_update.connect(self._tor_status_update) @@ -81,17 +85,19 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): def _tor_status_update(self, progress, summary): self.setValue(int(progress)) - self.setLabelText("<strong>{}</strong><br>{}".format(strings._('connecting_to_tor'), summary)) + self.setLabelText( + "<strong>{}</strong><br>{}".format(strings._("connecting_to_tor"), summary) + ) def _connected_to_tor(self): - self.common.log('TorConnectionDialog', '_connected_to_tor') + self.common.log("TorConnectionDialog", "_connected_to_tor") self.active = False # Close the dialog after connecting self.setValue(self.maximum()) def _canceled_connecting_to_tor(self): - self.common.log('TorConnectionDialog', '_canceled_connecting_to_tor') + self.common.log("TorConnectionDialog", "_canceled_connecting_to_tor") self.active = False self.onion.cleanup() @@ -99,12 +105,16 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): QtCore.QTimer.singleShot(1, self.cancel) def _error_connecting_to_tor(self, msg): - self.common.log('TorConnectionDialog', '_error_connecting_to_tor') + self.common.log("TorConnectionDialog", "_error_connecting_to_tor") self.active = False def alert_and_open_settings(): # Display the exception in an alert box - Alert(self.common, "{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings')), QtWidgets.QMessageBox.Warning) + Alert( + self.common, + "{}\n\n{}".format(msg, strings._("gui_tor_connection_error_settings")), + QtWidgets.QMessageBox.Warning, + ) # Open settings self.open_settings.emit() @@ -114,6 +124,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): # Cancel connecting to Tor QtCore.QTimer.singleShot(1, self.cancel) + class TorConnectionThread(QtCore.QThread): tor_status_update = QtCore.pyqtSignal(str, str) connected_to_tor = QtCore.pyqtSignal() @@ -125,7 +136,7 @@ class TorConnectionThread(QtCore.QThread): self.common = common - self.common.log('TorConnectionThread', '__init__') + self.common.log("TorConnectionThread", "__init__") self.settings = settings @@ -133,7 +144,7 @@ class TorConnectionThread(QtCore.QThread): self.onion = onion def run(self): - self.common.log('TorConnectionThread', 'run') + self.common.log("TorConnectionThread", "run") # Connect to the Onion try: @@ -144,11 +155,15 @@ class TorConnectionThread(QtCore.QThread): self.canceled_connecting_to_tor.emit() except BundledTorCanceled as e: - self.common.log('TorConnectionThread', 'run', 'caught exception: BundledTorCanceled') + self.common.log( + "TorConnectionThread", "run", "caught exception: BundledTorCanceled" + ) self.canceled_connecting_to_tor.emit() except Exception as e: - self.common.log('TorConnectionThread', 'run', 'caught exception: {}'.format(e.args[0])) + self.common.log( + "TorConnectionThread", "run", "caught exception: {}".format(e.args[0]) + ) self.error_connecting_to_tor.emit(str(e.args[0])) def _tor_status_update(self, progress, summary): diff --git a/onionshare_gui/update_checker.py b/onionshare_gui/update_checker.py index 1e37b73a..a8bd7979 100644 --- a/onionshare_gui/update_checker.py +++ b/onionshare_gui/update_checker.py @@ -27,21 +27,26 @@ from onionshare.onion import Onion from onionshare import strings + class UpdateCheckerCheckError(Exception): """ Error checking for updates because of some Tor connection issue, or because the OnionShare website is down. """ + pass + class UpdateCheckerInvalidLatestVersion(Exception): """ Successfully downloaded the latest version, but it doesn't appear to be a valid version string. """ + def __init__(self, latest_version): self.latest_version = latest_version + class UpdateChecker(QtCore.QObject): """ Load http://elx57ue5uyfplgva.onion/latest-version.txt to see what the latest @@ -50,6 +55,7 @@ class UpdateChecker(QtCore.QObject): Only check at most once per day, unless force is True. """ + update_available = QtCore.pyqtSignal(str, str, str) update_not_available = QtCore.pyqtSignal() update_error = QtCore.pyqtSignal() @@ -60,12 +66,12 @@ class UpdateChecker(QtCore.QObject): self.common = common - self.common.log('UpdateChecker', '__init__') + self.common.log("UpdateChecker", "__init__") self.onion = onion self.config = config def check(self, force=False, config=False): - self.common.log('UpdateChecker', 'check', 'force={}'.format(force)) + self.common.log("UpdateChecker", "check", "force={}".format(force)) # Load the settings settings = Settings(self.common, config) settings.load() @@ -77,7 +83,7 @@ class UpdateChecker(QtCore.QObject): check_for_updates = False # See if it's been 1 day since the last check - autoupdate_timestamp = settings.get('autoupdate_timestamp') + autoupdate_timestamp = settings.get("autoupdate_timestamp") if autoupdate_timestamp: last_checked = datetime.datetime.fromtimestamp(autoupdate_timestamp) now = datetime.datetime.now() @@ -90,45 +96,61 @@ class UpdateChecker(QtCore.QObject): # Check for updates if check_for_updates: - self.common.log('UpdateChecker', 'check', 'checking for updates') + self.common.log("UpdateChecker", "check", "checking for updates") # Download the latest-version file over Tor try: # User agent string includes OnionShare version and platform - user_agent = 'OnionShare {}, {}'.format(self.common.version, self.common.platform) + user_agent = "OnionShare {}, {}".format( + self.common.version, self.common.platform + ) # If the update is forced, add '?force=1' to the URL, to more # accurately measure daily users - path = '/latest-version.txt' + path = "/latest-version.txt" if force: - path += '?force=1' + path += "?force=1" - if Version(self.onion.tor_version) >= Version('0.3.2.9'): - onion_domain = 'lldan5gahapx5k7iafb3s4ikijc4ni7gx5iywdflkba5y2ezyg6sjgyd.onion' + if Version(self.onion.tor_version) >= Version("0.3.2.9"): + onion_domain = ( + "lldan5gahapx5k7iafb3s4ikijc4ni7gx5iywdflkba5y2ezyg6sjgyd.onion" + ) else: - onion_domain = 'elx57ue5uyfplgva.onion' + onion_domain = "elx57ue5uyfplgva.onion" - self.common.log('UpdateChecker', 'check', 'loading http://{}{}'.format(onion_domain, path)) + self.common.log( + "UpdateChecker", + "check", + "loading http://{}{}".format(onion_domain, path), + ) (socks_address, socks_port) = self.onion.get_tor_socks_port() socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port) s = socks.socksocket() - s.settimeout(15) # 15 second timeout + s.settimeout(15) # 15 second timeout s.connect((onion_domain, 80)) - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: {}\r\n'.format(onion_domain) - http_request += 'User-Agent: {}\r\n'.format(user_agent) - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) + http_request = "GET {} HTTP/1.0\r\n".format(path) + http_request += "Host: {}\r\n".format(onion_domain) + http_request += "User-Agent: {}\r\n".format(user_agent) + http_request += "\r\n" + s.sendall(http_request.encode("utf-8")) http_response = s.recv(1024) - latest_version = http_response[http_response.find(b'\r\n\r\n'):].strip().decode('utf-8') - - self.common.log('UpdateChecker', 'check', 'latest OnionShare version: {}'.format(latest_version)) + latest_version = ( + http_response[http_response.find(b"\r\n\r\n") :] + .strip() + .decode("utf-8") + ) + + self.common.log( + "UpdateChecker", + "check", + "latest OnionShare version: {}".format(latest_version), + ) except Exception as e: - self.common.log('UpdateChecker', 'check', '{}'.format(e)) + self.common.log("UpdateChecker", "check", "{}".format(e)) self.update_error.emit() raise UpdateCheckerCheckError @@ -140,22 +162,32 @@ class UpdateChecker(QtCore.QObject): raise UpdateCheckerInvalidLatestVersion(latest_version) # Update the last checked timestamp (dropping the seconds and milliseconds) - timestamp = datetime.datetime.now().replace(microsecond=0).replace(second=0).timestamp() + timestamp = ( + datetime.datetime.now() + .replace(microsecond=0) + .replace(second=0) + .timestamp() + ) # Re-load the settings first before saving, just in case they've changed since we started our thread settings.load() - settings.set('autoupdate_timestamp', timestamp) + settings.set("autoupdate_timestamp", timestamp) settings.save() # Do we need to update? - update_url = 'https://github.com/micahflee/onionshare/releases/tag/v{}'.format(latest_version) + update_url = "https://github.com/micahflee/onionshare/releases/tag/v{}".format( + latest_version + ) installed_version = self.common.version if installed_version < latest_version: - self.update_available.emit(update_url, installed_version, latest_version) + self.update_available.emit( + update_url, installed_version, latest_version + ) return # No updates are available self.update_not_available.emit() + class UpdateThread(QtCore.QThread): update_available = QtCore.pyqtSignal(str, str, str) update_not_available = QtCore.pyqtSignal() @@ -167,13 +199,13 @@ class UpdateThread(QtCore.QThread): self.common = common - self.common.log('UpdateThread', '__init__') + self.common.log("UpdateThread", "__init__") self.onion = onion self.config = config self.force = force def run(self): - self.common.log('UpdateThread', 'run') + self.common.log("UpdateThread", "run") u = UpdateChecker(self.common, self.onion, self.config) u.update_available.connect(self._update_available) @@ -182,28 +214,28 @@ class UpdateThread(QtCore.QThread): u.update_invalid_version.connect(self._update_invalid_version) try: - u.check(config=self.config,force=self.force) + u.check(config=self.config, force=self.force) except Exception as e: # If update check fails, silently ignore - self.common.log('UpdateThread', 'run', '{}'.format(e)) + self.common.log("UpdateThread", "run", "{}".format(e)) pass def _update_available(self, update_url, installed_version, latest_version): - self.common.log('UpdateThread', '_update_available') + self.common.log("UpdateThread", "_update_available") self.active = False self.update_available.emit(update_url, installed_version, latest_version) def _update_not_available(self): - self.common.log('UpdateThread', '_update_not_available') + self.common.log("UpdateThread", "_update_not_available") self.active = False self.update_not_available.emit() def _update_error(self): - self.common.log('UpdateThread', '_update_error') + self.common.log("UpdateThread", "_update_error") self.active = False self.update_error.emit() def _update_invalid_version(self, latest_version): - self.common.log('UpdateThread', '_update_invalid_version') + self.common.log("UpdateThread", "_update_invalid_version") self.active = False self.update_invalid_version.emit(latest_version) diff --git a/onionshare_gui/widgets.py b/onionshare_gui/widgets.py index 600165aa..d16485fe 100644 --- a/onionshare_gui/widgets.py +++ b/onionshare_gui/widgets.py @@ -19,19 +19,30 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. """ from PyQt5 import QtCore, QtWidgets, QtGui + class Alert(QtWidgets.QMessageBox): """ An alert box dialog. """ - def __init__(self, common, message, icon=QtWidgets.QMessageBox.NoIcon, buttons=QtWidgets.QMessageBox.Ok, autostart=True): + + def __init__( + self, + common, + message, + icon=QtWidgets.QMessageBox.NoIcon, + buttons=QtWidgets.QMessageBox.Ok, + autostart=True, + ): super(Alert, self).__init__(None) self.common = common - self.common.log('Alert', '__init__') + self.common.log("Alert", "__init__") self.setWindowTitle("OnionShare") - self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) + self.setWindowIcon( + QtGui.QIcon(self.common.get_resource_path("images/logo.png")) + ) self.setText(message) self.setIcon(icon) self.setStandardButtons(buttons) @@ -49,11 +60,12 @@ class AddFileDialog(QtWidgets.QFileDialog): This is because the macOS sandbox requires native dialogs, and this is a Qt5 dialog. """ + def __init__(self, common, *args, **kwargs): QtWidgets.QFileDialog.__init__(self, *args, **kwargs) self.common = common - self.common.log('AddFileDialog', '__init__') + self.common.log("AddFileDialog", "__init__") self.setOption(self.DontUseNativeDialog, True) self.setOption(self.ReadOnly, True) @@ -65,5 +77,5 @@ class AddFileDialog(QtWidgets.QFileDialog): list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) def accept(self): - self.common.log('AddFileDialog', 'accept') + self.common.log("AddFileDialog", "accept") QtWidgets.QDialog.accept(self) |