From 32108dcca2d6ace7663cfda6c58d7fad9713f42f Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 8 Nov 2017 20:25:59 +1100 Subject: [PATCH 01/22] Implements a shutdown timer to stop a share automatically (downloaded or not) after N hours --- onionshare/__init__.py | 18 +++++++++++++--- onionshare/common.py | 16 ++++++++++++++ onionshare/onionshare.py | 9 +++++++- onionshare/web.py | 10 ++++++++- onionshare_gui/__init__.py | 4 +++- onionshare_gui/onionshare_gui.py | 10 ++++++++- onionshare_gui/server_status.py | 36 ++++++++++++++++++++++++++++++++ share/locale/en.json | 4 ++++ 8 files changed, 100 insertions(+), 7 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 8a784e4b..5371f8ab 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -42,6 +42,7 @@ def main(cwd=None): parser = argparse.ArgumentParser() parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only")) parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open")) + parser.add_argument('--shutdown-timeout', metavar='shutdown_timeout', dest='shutdown_timeout', help=strings._("help_shutdown_timeout")) parser.add_argument('--stealth', action='store_true', dest='stealth', help=strings._("help_stealth")) parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug")) parser.add_argument('--config', metavar='config', default=False, help=strings._('help_config')) @@ -55,6 +56,7 @@ def main(cwd=None): local_only = bool(args.local_only) debug = bool(args.debug) stay_open = bool(args.stay_open) + shutdown_timeout = float(args.shutdown_timeout) stealth = bool(args.stealth) config = args.config @@ -87,7 +89,7 @@ def main(cwd=None): # Start the onionshare app try: - app = OnionShare(onion, local_only, stay_open) + app = OnionShare(onion, local_only, stay_open, shutdown_timeout) app.set_stealth(stealth) app.start_onion_service() except KeyboardInterrupt: @@ -106,7 +108,7 @@ def main(cwd=None): print('') # Start OnionShare http service in new thread - t = threading.Thread(target=web.start, args=(app.port, app.stay_open)) + t = threading.Thread(target=web.start, args=(app.port, app.stay_open, app.shutdown_timeout)) t.daemon = True t.start() @@ -114,6 +116,10 @@ def main(cwd=None): # Wait for web.generate_slug() to finish running time.sleep(0.2) + # start shutdown timer thread + if app.shutdown_timeout > 0: + app.shutdown_timer.start() + if(stealth): print(strings._("give_this_url_stealth")) print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug)) @@ -126,9 +132,15 @@ def main(cwd=None): # Wait for app to close while t.is_alive(): + if app.shutdown_timeout > 0: + # if the shutdown timer was set and has run out, stop the server + if not app.shutdown_timer.is_alive(): + print(strings._("close_on_timeout")) + web.stop(app.port) + break # Allow KeyboardInterrupt exception to be handled with threads # https://stackoverflow.com/questions/3788208/python-threading-ignores-keyboardinterrupt-exception - time.sleep(100) + time.sleep(0.2) except KeyboardInterrupt: web.stop(app.port) finally: diff --git a/onionshare/common.py b/onionshare/common.py index 89d4695f..4a5c388e 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -27,6 +27,7 @@ import random import socket import sys import tempfile +import threading import time import zipfile @@ -254,3 +255,18 @@ class ZipWriter(object): Close the zip archive. """ self.z.close() + + +class close_after_seconds(threading.Thread): + """ + Background thread sleeps t hours and returns. + """ + def __init__(self, time): + threading.Thread.__init__(self) + self.setDaemon(True) + self.time = time + + def run(self): + log('Shutdown Timer', 'Server will shut down after {} seconds'.format(3600 * self.time)) + time.sleep(3600 * self.time) # seconds -> hours + return 1 diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index 166afffc..44941f97 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -27,7 +27,7 @@ class OnionShare(object): OnionShare is the main application class. Pass in options and run start_onion_service and it will do the magic. """ - def __init__(self, onion, local_only=False, stay_open=False): + def __init__(self, onion, local_only=False, stay_open=False, shutdown_timeout=0): common.log('OnionShare', '__init__') # The Onion object @@ -46,6 +46,11 @@ class OnionShare(object): # automatically close when download is finished self.stay_open = stay_open + # optionally shut down after N hours + self.shutdown_timeout = shutdown_timeout + # init timing thread + self.shutdown_timer = None + def set_stealth(self, stealth): common.log('OnionShare', 'set_stealth', 'stealth={}'.format(stealth)) @@ -65,6 +70,8 @@ class OnionShare(object): self.onion_host = '127.0.0.1:{0:d}'.format(self.port) return + if self.shutdown_timeout > 0: + self.shutdown_timer = common.close_after_seconds(self.shutdown_timeout) self.onion_host = self.onion.start_onion_service(self.port) if self.stealth: diff --git a/onionshare/web.py b/onionshare/web.py index aec86bf4..55d4a96f 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -135,6 +135,13 @@ def get_stay_open(): """ return stay_open +shutdown_timeout = 0 +def set_shutdown_timeout(new_shutdown_timeout): + """ + Set shutdown_timeout variable. + """ + global shutdown_timeout + shutdown_timeout = new_shutdown_timeout # Are we running in GUI mode? gui_mode = False @@ -361,13 +368,14 @@ def force_shutdown(): func() -def start(port, stay_open=False): +def start(port, stay_open=False, shutdown_timeout=0): """ Start the flask web server. """ generate_slug() set_stay_open(stay_open) + set_shutdown_timeout(shutdown_timeout) # In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220) if os.path.exists('/usr/share/anon-ws-base-files/workstation'): diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 4aa4ff83..cc752d3e 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -64,6 +64,7 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only")) parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open")) + parser.add_argument('--shutdown-timeout', metavar='shutdown_timeout', dest='shutdown_timeout', default=0, help=strings._("help_shutdown_timeout")) parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug")) parser.add_argument('--filenames', metavar='filenames', nargs='+', help=strings._('help_filename')) parser.add_argument('--config', metavar='config', default=False, help=strings._('help_config')) @@ -78,6 +79,7 @@ def main(): local_only = bool(args.local_only) stay_open = bool(args.stay_open) + shutdown_timeout = float(args.shutdown_timeout) debug = bool(args.debug) # Debug mode? @@ -103,7 +105,7 @@ def main(): # Start the OnionShare app web.set_stay_open(stay_open) - app = OnionShare(onion, local_only, stay_open) + app = OnionShare(onion, local_only, stay_open, shutdown_timeout) # Launch the gui gui = OnionShareGui(onion, qtapp, app, filenames, config) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 8e84acac..d629bd58 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -258,7 +258,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.app.stay_open = not self.settings.get('close_after_first_download') # start onionshare http service in new thread - t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open)) + t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open, self.app.shutdown_timeout)) t.daemon = True t.start() # wait for modules in thread to load, preventing a thread-related cx_Freeze crash @@ -371,6 +371,14 @@ class OnionShareGui(QtWidgets.QMainWindow): Check for messages communicated from the web app, and update the GUI accordingly. """ self.update() + + # If the auto-shutdown timer has stopped, stop the server + if self.server_status.status == self.server_status.STATUS_STARTED: + if self.app.shutdown_timer and self.server_status.server_shutdown_timeout.value() > 0: + if not self.app.shutdown_timer.is_alive(): + self.server_status.stop_server() + self.status_bar.showMessage(strings._('close_on_timeout',True)) + # scroll to the bottom of the dl progress bar log pane # if a new download has been added if self.new_download: diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 87143725..35c49072 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -44,6 +44,20 @@ class ServerStatus(QtWidgets.QVBoxLayout): self.web = web self.file_selection = file_selection + # shutdown timeout layout + self.server_shutdown_timeout_checkbox = QtWidgets.QCheckBox() + self.server_shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.server_shutdown_timeout_checkbox.toggled.connect(self.shutdown_timeout_toggled) + self.server_shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_choice", True)) + self.server_shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) + self.server_shutdown_timeout = QtWidgets.QDoubleSpinBox() + self.server_shutdown_timeout.setRange(0,100) + self.server_shutdown_timeout_label.hide() + self.server_shutdown_timeout.hide() + shutdown_timeout_layout_group = QtWidgets.QHBoxLayout() + shutdown_timeout_layout_group.addWidget(self.server_shutdown_timeout_checkbox) + shutdown_timeout_layout_group.addWidget(self.server_shutdown_timeout_label) + shutdown_timeout_layout_group.addWidget(self.server_shutdown_timeout) # server layout self.status_image_stopped = QtGui.QImage(common.get_resource_path('images/server_stopped.png')) self.status_image_working = QtGui.QImage(common.get_resource_path('images/server_working.png')) @@ -72,11 +86,25 @@ class ServerStatus(QtWidgets.QVBoxLayout): url_layout.addWidget(self.copy_hidservauth_button) # add the widgets + self.addLayout(shutdown_timeout_layout_group) self.addLayout(server_layout) self.addLayout(url_layout) self.update() + def shutdown_timeout_toggled(self, checked): + """ + Shutdown timer option was toggled. If checked, hide the option and show the timer settings. + """ + if checked: + self.server_shutdown_timeout_checkbox.hide() + self.server_shutdown_timeout_label.show() + self.server_shutdown_timeout.show() + else: + self.server_shutdown_timeout_checkbox.show() + self.server_shutdown_timeout_label.hide() + self.server_shutdown_timeout.hide() + def update(self): """ Update the GUI elements based on the current state. @@ -116,12 +144,16 @@ class ServerStatus(QtWidgets.QVBoxLayout): if self.status == self.STATUS_STOPPED: self.server_button.setEnabled(True) self.server_button.setText(strings._('gui_start_server', True)) + self.server_shutdown_timeout.setEnabled(True) + self.server_shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Unchecked) elif self.status == self.STATUS_STARTED: self.server_button.setEnabled(True) self.server_button.setText(strings._('gui_stop_server', True)) + self.server_shutdown_timeout.setEnabled(False) else: self.server_button.setEnabled(False) self.server_button.setText(strings._('gui_please_wait')) + self.server_shutdown_timeout.setEnabled(False) def server_button_clicked(self): """ @@ -145,6 +177,10 @@ class ServerStatus(QtWidgets.QVBoxLayout): The server has finished starting. """ self.status = self.STATUS_STARTED + # Set the shutdown timeout value + if self.server_shutdown_timeout.value() > 0: + self.app.shutdown_timer = common.close_after_seconds(self.server_shutdown_timeout.value()) + self.app.shutdown_timer.start() self.copy_url() self.update() diff --git a/share/locale/en.json b/share/locale/en.json index cfd80455..0f2a3518 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -12,6 +12,7 @@ "not_a_readable_file": "{0:s} is not a readable file.", "download_page_loaded": "Download page loaded", "other_page_loaded": "URL loaded", + "close_on_timeout": "Closing automatically because timeout was reached", "closing_automatically": "Closing automatically because download finished", "large_filesize": "Warning: Sending large files could take hours", "error_tails_invalid_port": "Invalid value, port must be an integer", @@ -25,6 +26,7 @@ "systray_download_canceled_message": "The user canceled the download", "help_local_only": "Do not attempt to use tor: for development only", "help_stay_open": "Keep onion service running after download has finished", + "help_shutdown_timeout": "Shut down the onion service after N hours", "help_transparent_torification": "My system is transparently torified", "help_stealth": "Create stealth onion service (advanced)", "help_debug": "Log application errors to stdout, and log web errors to disk", @@ -89,6 +91,8 @@ "gui_settings_button_save": "Save", "gui_settings_button_cancel": "Cancel", "gui_settings_button_help": "Help", + "gui_settings_shutdown_timeout_choice": "Set auto-stop timer?", + "gui_settings_shutdown_timeout": "Auto-stop share in (hours):", "settings_saved": "Settings saved to {}", "settings_error_unknown": "Can't connect to Tor controller because the settings don't make sense.", "settings_error_automatic": "Can't connect to Tor controller. Is Tor Browser running in the background? If you don't have it you can get it from:\nhttps://www.torproject.org/.", From a4b8a71c68fb160740beaec298c49b546037df78 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 9 Nov 2017 07:12:00 +1100 Subject: [PATCH 02/22] remove shutdown_timeout logic in the web server, it's not actually needed --- onionshare/__init__.py | 2 +- onionshare/web.py | 11 +---------- onionshare_gui/onionshare_gui.py | 2 +- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 5371f8ab..138c03a5 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -108,7 +108,7 @@ def main(cwd=None): print('') # Start OnionShare http service in new thread - t = threading.Thread(target=web.start, args=(app.port, app.stay_open, app.shutdown_timeout)) + t = threading.Thread(target=web.start, args=(app.port, app.stay_open)) t.daemon = True t.start() diff --git a/onionshare/web.py b/onionshare/web.py index 55d4a96f..5705bf90 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -135,14 +135,6 @@ def get_stay_open(): """ return stay_open -shutdown_timeout = 0 -def set_shutdown_timeout(new_shutdown_timeout): - """ - Set shutdown_timeout variable. - """ - global shutdown_timeout - shutdown_timeout = new_shutdown_timeout - # Are we running in GUI mode? gui_mode = False def set_gui_mode(): @@ -368,14 +360,13 @@ def force_shutdown(): func() -def start(port, stay_open=False, shutdown_timeout=0): +def start(port, stay_open=False): """ Start the flask web server. """ generate_slug() set_stay_open(stay_open) - set_shutdown_timeout(shutdown_timeout) # In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220) if os.path.exists('/usr/share/anon-ws-base-files/workstation'): diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index d629bd58..151d2468 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -258,7 +258,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.app.stay_open = not self.settings.get('close_after_first_download') # start onionshare http service in new thread - t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open, self.app.shutdown_timeout)) + t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open)) t.daemon = True t.start() # wait for modules in thread to load, preventing a thread-related cx_Freeze crash From 481f33c8223b955793aa66d00197774784755db8 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 9 Nov 2017 11:29:55 +1100 Subject: [PATCH 03/22] use QDateTimeEdit instead of a spinbox for selecting a future date/time to auto-stop share --- onionshare/common.py | 4 ++-- onionshare_gui/onionshare_gui.py | 2 +- onionshare_gui/server_status.py | 12 ++++++++---- share/locale/en.json | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index 4a5c388e..8f4d257e 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -267,6 +267,6 @@ class close_after_seconds(threading.Thread): self.time = time def run(self): - log('Shutdown Timer', 'Server will shut down after {} seconds'.format(3600 * self.time)) - time.sleep(3600 * self.time) # seconds -> hours + log('Shutdown Timer', 'Server will shut down after {} seconds'.format(self.time)) + time.sleep(self.time) return 1 diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 151d2468..06d17df6 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -374,7 +374,7 @@ class OnionShareGui(QtWidgets.QMainWindow): # If the auto-shutdown timer has stopped, stop the server if self.server_status.status == self.server_status.STATUS_STARTED: - if self.app.shutdown_timer and self.server_status.server_shutdown_timeout.value() > 0: + if self.app.shutdown_timer and self.server_status.timeout > 0: if not self.app.shutdown_timer.is_alive(): self.server_status.stop_server() self.status_bar.showMessage(strings._('close_on_timeout',True)) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 35c49072..7bf8011a 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -50,8 +50,9 @@ class ServerStatus(QtWidgets.QVBoxLayout): self.server_shutdown_timeout_checkbox.toggled.connect(self.shutdown_timeout_toggled) self.server_shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_choice", True)) self.server_shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) - self.server_shutdown_timeout = QtWidgets.QDoubleSpinBox() - self.server_shutdown_timeout.setRange(0,100) + self.server_shutdown_timeout = QtWidgets.QDateTimeEdit() + self.server_shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime()) + self.server_shutdown_timeout.setCurrentSectionIndex(4) self.server_shutdown_timeout_label.hide() self.server_shutdown_timeout.hide() shutdown_timeout_layout_group = QtWidgets.QHBoxLayout() @@ -177,9 +178,12 @@ class ServerStatus(QtWidgets.QVBoxLayout): The server has finished starting. """ self.status = self.STATUS_STARTED + # Convert the date value to seconds between now and then + now = QtCore.QDateTime.currentDateTime() + self.timeout = now.secsTo(self.server_shutdown_timeout.dateTime()) # Set the shutdown timeout value - if self.server_shutdown_timeout.value() > 0: - self.app.shutdown_timer = common.close_after_seconds(self.server_shutdown_timeout.value()) + if self.timeout > 0: + self.app.shutdown_timer = common.close_after_seconds(self.timeout) self.app.shutdown_timer.start() self.copy_url() self.update() diff --git a/share/locale/en.json b/share/locale/en.json index 0f2a3518..2965869e 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -92,7 +92,7 @@ "gui_settings_button_cancel": "Cancel", "gui_settings_button_help": "Help", "gui_settings_shutdown_timeout_choice": "Set auto-stop timer?", - "gui_settings_shutdown_timeout": "Auto-stop share in (hours):", + "gui_settings_shutdown_timeout": "Auto-stop share at:", "settings_saved": "Settings saved to {}", "settings_error_unknown": "Can't connect to Tor controller because the settings don't make sense.", "settings_error_automatic": "Can't connect to Tor controller. Is Tor Browser running in the background? If you don't have it you can get it from:\nhttps://www.torproject.org/.", From 9657df282ef5c7751821a784a65123676c26c667 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 9 Nov 2017 11:34:59 +1100 Subject: [PATCH 04/22] ensure the shutdown timeout can't go backwards in time --- onionshare_gui/server_status.py | 1 + 1 file changed, 1 insertion(+) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 7bf8011a..c6facdba 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -52,6 +52,7 @@ class ServerStatus(QtWidgets.QVBoxLayout): self.server_shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) self.server_shutdown_timeout = QtWidgets.QDateTimeEdit() self.server_shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime()) + self.server_shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime()) self.server_shutdown_timeout.setCurrentSectionIndex(4) self.server_shutdown_timeout_label.hide() self.server_shutdown_timeout.hide() From d49e7cf1d1f693fb910aea4d7e84f117906e1e8e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 9 Nov 2017 11:46:26 +1100 Subject: [PATCH 05/22] more UI fixes - hide checkbox if server is working/started and it was not checked. Ensure we only set the timer if the timeout checkbox was checked to begin with --- onionshare_gui/server_status.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index c6facdba..7e6a6488 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -152,6 +152,12 @@ class ServerStatus(QtWidgets.QVBoxLayout): self.server_button.setEnabled(True) self.server_button.setText(strings._('gui_stop_server', True)) self.server_shutdown_timeout.setEnabled(False) + self.server_shutdown_timeout_checkbox.hide() + elif self.status == self.STATUS_WORKING: + self.server_button.setEnabled(False) + self.server_button.setText(strings._('gui_please_wait')) + self.server_shutdown_timeout.setEnabled(False) + self.server_shutdown_timeout_checkbox.hide() else: self.server_button.setEnabled(False) self.server_button.setText(strings._('gui_please_wait')) @@ -179,13 +185,14 @@ class ServerStatus(QtWidgets.QVBoxLayout): The server has finished starting. """ self.status = self.STATUS_STARTED - # Convert the date value to seconds between now and then - now = QtCore.QDateTime.currentDateTime() - self.timeout = now.secsTo(self.server_shutdown_timeout.dateTime()) - # Set the shutdown timeout value - if self.timeout > 0: - self.app.shutdown_timer = common.close_after_seconds(self.timeout) - self.app.shutdown_timer.start() + if self.server_shutdown_timeout_checkbox.isChecked(): + # Convert the date value to seconds between now and then + now = QtCore.QDateTime.currentDateTime() + self.timeout = now.secsTo(self.server_shutdown_timeout.dateTime()) + # Set the shutdown timeout value + if self.timeout > 0: + self.app.shutdown_timer = common.close_after_seconds(self.timeout) + self.app.shutdown_timer.start() self.copy_url() self.update() From 2dd6c5527dd87bfc2fa012615245191c559a6bbe Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 9 Nov 2017 11:49:01 +1100 Subject: [PATCH 06/22] ensure the timeout checkbox is always shown if the server is stopped and it was not already checked --- onionshare_gui/server_status.py | 1 + 1 file changed, 1 insertion(+) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 7e6a6488..4750b7b6 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -147,6 +147,7 @@ class ServerStatus(QtWidgets.QVBoxLayout): self.server_button.setEnabled(True) self.server_button.setText(strings._('gui_start_server', True)) self.server_shutdown_timeout.setEnabled(True) + self.server_shutdown_timeout_checkbox.show() self.server_shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Unchecked) elif self.status == self.STATUS_STARTED: self.server_button.setEnabled(True) From 44fb6c69ae485f5ac731f96abe04e6bfd30b2c34 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 9 Nov 2017 11:56:02 +1100 Subject: [PATCH 07/22] set the timeout default to 5 minutes into the future for convenience --- onionshare_gui/server_status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 4750b7b6..aa27f577 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -51,7 +51,7 @@ class ServerStatus(QtWidgets.QVBoxLayout): self.server_shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_choice", True)) self.server_shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) self.server_shutdown_timeout = QtWidgets.QDateTimeEdit() - self.server_shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime()) + self.server_shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) self.server_shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime()) self.server_shutdown_timeout.setCurrentSectionIndex(4) self.server_shutdown_timeout_label.hide() From b618d8c15d59f20aa67bd0f4ec6d68d7e9eea7f9 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 9 Nov 2017 12:35:38 +1100 Subject: [PATCH 08/22] Calculate the time difference in a more appropriate spot (rather than on clicking Start, but when the server is ready, as it may have taken some time, but should still stop at the nominated time) --- onionshare_gui/onionshare_gui.py | 13 +++++++++++-- onionshare_gui/server_status.py | 8 -------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 06d17df6..0c54303b 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -316,6 +316,15 @@ class OnionShareGui(QtWidgets.QMainWindow): self.filesize_warning.setText(strings._("large_filesize", True)) self.filesize_warning.show() + if self.server_status.server_shutdown_timeout_checkbox.isChecked(): + # Convert the date value to seconds between now and then + now = QtCore.QDateTime.currentDateTime() + self.timeout = now.secsTo(self.server_status.server_shutdown_timeout.dateTime()) + # Set the shutdown timeout value + if self.timeout > 0: + self.app.shutdown_timer = common.close_after_seconds(self.timeout) + self.app.shutdown_timer.start() + def start_server_error(self, error): """ If there's an error when trying to start the onion service @@ -374,9 +383,9 @@ class OnionShareGui(QtWidgets.QMainWindow): # If the auto-shutdown timer has stopped, stop the server if self.server_status.status == self.server_status.STATUS_STARTED: - if self.app.shutdown_timer and self.server_status.timeout > 0: + if self.app.shutdown_timer and self.timeout > 0: if not self.app.shutdown_timer.is_alive(): - self.server_status.stop_server() + self.stop_server() self.status_bar.showMessage(strings._('close_on_timeout',True)) # scroll to the bottom of the dl progress bar log pane diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index aa27f577..9f7f6e3b 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -186,14 +186,6 @@ class ServerStatus(QtWidgets.QVBoxLayout): The server has finished starting. """ self.status = self.STATUS_STARTED - if self.server_shutdown_timeout_checkbox.isChecked(): - # Convert the date value to seconds between now and then - now = QtCore.QDateTime.currentDateTime() - self.timeout = now.secsTo(self.server_shutdown_timeout.dateTime()) - # Set the shutdown timeout value - if self.timeout > 0: - self.app.shutdown_timer = common.close_after_seconds(self.timeout) - self.app.shutdown_timer.start() self.copy_url() self.update() From 9aabc51edc81126f6ca0f697b92b7858760f9b9a Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 9 Nov 2017 12:52:44 +1100 Subject: [PATCH 09/22] On subsequent shares, the default time should nudge 5 minutes ahead of the current time again, instead of 5 minutes since the time OnionShare was opened --- onionshare_gui/server_status.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 9f7f6e3b..99281773 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -101,6 +101,8 @@ class ServerStatus(QtWidgets.QVBoxLayout): if checked: self.server_shutdown_timeout_checkbox.hide() self.server_shutdown_timeout_label.show() + # Reset the default timer to 5 minutes into the future after toggling the option on + self.server_shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) self.server_shutdown_timeout.show() else: self.server_shutdown_timeout_checkbox.show() From ac0e375a4b6459617e67d17af1f51482f1167da0 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 9 Nov 2017 17:26:32 +1100 Subject: [PATCH 10/22] Various safety checks to prevent a share from starting after the timeout has expired. Also enforce that a timeout lands right on the minute and not precisely when the user clicks start (e.g mid-minute), to avoid confusion that a share might be lingering longer than desired --- onionshare_gui/onionshare_gui.py | 12 +++++++++--- onionshare_gui/server_status.py | 28 +++++++++++++++++++++++++--- share/locale/en.json | 4 +++- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 0c54303b..9b7a35bd 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -316,14 +316,18 @@ class OnionShareGui(QtWidgets.QMainWindow): self.filesize_warning.setText(strings._("large_filesize", True)) self.filesize_warning.show() - if self.server_status.server_shutdown_timeout_checkbox.isChecked(): + if self.server_status.timer_enabled: # Convert the date value to seconds between now and then now = QtCore.QDateTime.currentDateTime() - self.timeout = now.secsTo(self.server_status.server_shutdown_timeout.dateTime()) + self.timeout = now.secsTo(self.server_status.timeout) # Set the shutdown timeout value if self.timeout > 0: self.app.shutdown_timer = common.close_after_seconds(self.timeout) self.app.shutdown_timer.start() + # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start. + else: + self.stop_server() + self.start_server_error(strings._('gui_server_started_after_timeout')) def start_server_error(self, error): """ @@ -383,10 +387,11 @@ class OnionShareGui(QtWidgets.QMainWindow): # If the auto-shutdown timer has stopped, stop the server if self.server_status.status == self.server_status.STATUS_STARTED: - if self.app.shutdown_timer and self.timeout > 0: + if self.app.shutdown_timer and self.server_status.timer_enabled and self.timeout > 0: if not self.app.shutdown_timer.is_alive(): self.stop_server() self.status_bar.showMessage(strings._('close_on_timeout',True)) + self.server_status.shutdown_timeout_reset() # scroll to the bottom of the dl progress bar log pane # if a new download has been added @@ -432,6 +437,7 @@ class OnionShareGui(QtWidgets.QMainWindow): # close on finish? if not web.get_stay_open(): self.server_status.stop_server() + self.server_status.shutdown_timeout_reset() elif event["type"] == web.REQUEST_CANCELED: self.downloads.cancel_download(event["data"]["id"]) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 99281773..297bb791 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ import platform +from .alert import Alert from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings, common @@ -44,15 +45,19 @@ class ServerStatus(QtWidgets.QVBoxLayout): self.web = web self.file_selection = file_selection - # shutdown timeout layout + # Helper boolean as this is used in a few places + self.timer_enabled = False + # Shutdown timeout layout self.server_shutdown_timeout_checkbox = QtWidgets.QCheckBox() self.server_shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Unchecked) self.server_shutdown_timeout_checkbox.toggled.connect(self.shutdown_timeout_toggled) self.server_shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_choice", True)) self.server_shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) self.server_shutdown_timeout = QtWidgets.QDateTimeEdit() + # Set proposed timeout to be 5 minutes into the future self.server_shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) - self.server_shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime()) + # Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 2 min from now + self.server_shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120)) self.server_shutdown_timeout.setCurrentSectionIndex(4) self.server_shutdown_timeout_label.hide() self.server_shutdown_timeout.hide() @@ -99,16 +104,26 @@ class ServerStatus(QtWidgets.QVBoxLayout): Shutdown timer option was toggled. If checked, hide the option and show the timer settings. """ if checked: + self.timer_enabled = True + # Hide the checkbox, show the options self.server_shutdown_timeout_checkbox.hide() self.server_shutdown_timeout_label.show() # Reset the default timer to 5 minutes into the future after toggling the option on self.server_shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) self.server_shutdown_timeout.show() else: + self.timer_enabled = False self.server_shutdown_timeout_checkbox.show() self.server_shutdown_timeout_label.hide() self.server_shutdown_timeout.hide() + def shutdown_timeout_reset(self): + """ + Reset the timeout in the UI after stopping a share + """ + self.server_shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + self.server_shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120)) + def update(self): """ Update the GUI elements based on the current state. @@ -171,9 +186,16 @@ class ServerStatus(QtWidgets.QVBoxLayout): Toggle starting or stopping the server. """ if self.status == self.STATUS_STOPPED: - self.start_server() + # Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen + self.timeout = self.server_shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0) + # If the timeout has actually passed already before the user hit Start, refuse to start the server. + if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout: + Alert(strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning)) + else: + self.start_server() elif self.status == self.STATUS_STARTED: self.stop_server() + self.shutdown_timeout_reset() def start_server(self): """ diff --git a/share/locale/en.json b/share/locale/en.json index 2965869e..27f4dab5 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -115,5 +115,7 @@ "gui_tor_connection_ask": "Would you like to open OnionShare settings to troubleshoot connecting to Tor?", "gui_tor_connection_ask_open_settings": "Open Settings", "gui_tor_connection_ask_quit": "Quit", - "gui_tor_connection_error_settings": "Try adjusting how OnionShare connects to the Tor network in Settings." + "gui_tor_connection_error_settings": "Try adjusting how OnionShare connects to the Tor network in Settings.", + "gui_server_started_after_timeout": "The server started after your chosen auto-timeout.\nPlease start a new share.", + "gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing." } From 877459a560f6cbeff498c46b0f20d27025411bcb Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 9 Nov 2017 18:01:09 +1100 Subject: [PATCH 11/22] undo removal of newline in web.py --- onionshare/web.py | 1 + 1 file changed, 1 insertion(+) diff --git a/onionshare/web.py b/onionshare/web.py index 5705bf90..aec86bf4 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -135,6 +135,7 @@ def get_stay_open(): """ return stay_open + # Are we running in GUI mode? gui_mode = False def set_gui_mode(): From 882057eafc8fdbfab3f08d669a60ef7ca182caf5 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 9 Nov 2017 18:23:11 +1100 Subject: [PATCH 12/22] only prevent the share from starting when the timeout has expired, if the timeout feature was even set at all --- onionshare_gui/onionshare_gui.py | 11 ++++++----- onionshare_gui/server_status.py | 13 ++++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 9b7a35bd..508db655 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -387,11 +387,12 @@ class OnionShareGui(QtWidgets.QMainWindow): # If the auto-shutdown timer has stopped, stop the server if self.server_status.status == self.server_status.STATUS_STARTED: - if self.app.shutdown_timer and self.server_status.timer_enabled and self.timeout > 0: - if not self.app.shutdown_timer.is_alive(): - self.stop_server() - self.status_bar.showMessage(strings._('close_on_timeout',True)) - self.server_status.shutdown_timeout_reset() + if self.app.shutdown_timer and self.server_status.timer_enabled: + if self.timeout > 0: + if not self.app.shutdown_timer.is_alive(): + self.stop_server() + self.status_bar.showMessage(strings._('close_on_timeout',True)) + self.server_status.shutdown_timeout_reset() # scroll to the bottom of the dl progress bar log pane # if a new download has been added diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 297bb791..452c2062 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -186,11 +186,14 @@ class ServerStatus(QtWidgets.QVBoxLayout): Toggle starting or stopping the server. """ if self.status == self.STATUS_STOPPED: - # Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen - self.timeout = self.server_shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0) - # If the timeout has actually passed already before the user hit Start, refuse to start the server. - if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout: - Alert(strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning)) + if self.timer_enabled: + # Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen + self.timeout = self.server_shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0) + # If the timeout has actually passed already before the user hit Start, refuse to start the server. + if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout: + Alert(strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning)) + else: + self.start_server() else: self.start_server() elif self.status == self.STATUS_STARTED: From f220058c63b4d796da36b77938b56c151c0441a2 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 9 Nov 2017 19:50:50 +1100 Subject: [PATCH 13/22] No longer treating shutdown_timeout as a float, but an int of seconds --- onionshare/__init__.py | 4 ++-- onionshare_gui/__init__.py | 2 +- share/locale/en.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 138c03a5..11d2521b 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -42,7 +42,7 @@ def main(cwd=None): parser = argparse.ArgumentParser() parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only")) parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open")) - parser.add_argument('--shutdown-timeout', metavar='shutdown_timeout', dest='shutdown_timeout', help=strings._("help_shutdown_timeout")) + parser.add_argument('--shutdown-timeout', metavar='shutdown_timeout', dest='shutdown_timeout', default=0, help=strings._("help_shutdown_timeout")) parser.add_argument('--stealth', action='store_true', dest='stealth', help=strings._("help_stealth")) parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug")) parser.add_argument('--config', metavar='config', default=False, help=strings._('help_config')) @@ -56,7 +56,7 @@ def main(cwd=None): local_only = bool(args.local_only) debug = bool(args.debug) stay_open = bool(args.stay_open) - shutdown_timeout = float(args.shutdown_timeout) + shutdown_timeout = int(args.shutdown_timeout) stealth = bool(args.stealth) config = args.config diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index cc752d3e..108e711f 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -79,7 +79,7 @@ def main(): local_only = bool(args.local_only) stay_open = bool(args.stay_open) - shutdown_timeout = float(args.shutdown_timeout) + shutdown_timeout = int(args.shutdown_timeout) debug = bool(args.debug) # Debug mode? diff --git a/share/locale/en.json b/share/locale/en.json index 27f4dab5..21282589 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -26,7 +26,7 @@ "systray_download_canceled_message": "The user canceled the download", "help_local_only": "Do not attempt to use tor: for development only", "help_stay_open": "Keep onion service running after download has finished", - "help_shutdown_timeout": "Shut down the onion service after N hours", + "help_shutdown_timeout": "Shut down the onion service after N seconds", "help_transparent_torification": "My system is transparently torified", "help_stealth": "Create stealth onion service (advanced)", "help_debug": "Log application errors to stdout, and log web errors to disk", From 275f345604fb2fd344c50dadf52b513c9c7ab2e0 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 11 Nov 2017 17:12:10 +1100 Subject: [PATCH 14/22] better metavar for --shutdown-timeout arg --- onionshare/__init__.py | 2 +- onionshare_gui/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 11d2521b..c071a441 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -42,7 +42,7 @@ def main(cwd=None): parser = argparse.ArgumentParser() parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only")) parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open")) - parser.add_argument('--shutdown-timeout', metavar='shutdown_timeout', dest='shutdown_timeout', default=0, help=strings._("help_shutdown_timeout")) + parser.add_argument('--shutdown-timeout', metavar='', dest='shutdown_timeout', default=0, help=strings._("help_shutdown_timeout")) parser.add_argument('--stealth', action='store_true', dest='stealth', help=strings._("help_stealth")) parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug")) parser.add_argument('--config', metavar='config', default=False, help=strings._('help_config')) diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 108e711f..b7881b7c 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -64,7 +64,7 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only")) parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open")) - parser.add_argument('--shutdown-timeout', metavar='shutdown_timeout', dest='shutdown_timeout', default=0, help=strings._("help_shutdown_timeout")) + parser.add_argument('--shutdown-timeout', metavar='', dest='shutdown_timeout', default=0, help=strings._("help_shutdown_timeout")) parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug")) parser.add_argument('--filenames', metavar='filenames', nargs='+', help=strings._('help_filename')) parser.add_argument('--config', metavar='config', default=False, help=strings._('help_config')) From 624028e49f2b62662b540e20fc9f6b4b09600087 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 12 Nov 2017 10:40:04 +1100 Subject: [PATCH 15/22] Format the argparser output better, to deal with the longer argument names --- onionshare/__init__.py | 2 +- onionshare_gui/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index c071a441..6ab030ef 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -39,7 +39,7 @@ def main(cwd=None): os.chdir(cwd) # Parse arguments - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=28)) parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only")) parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open")) parser.add_argument('--shutdown-timeout', metavar='', dest='shutdown_timeout', default=0, help=strings._("help_shutdown_timeout")) diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index b7881b7c..83d03cc0 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -61,7 +61,7 @@ def main(): qtapp = Application() # Parse arguments - parser = argparse.ArgumentParser() + 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=strings._("help_local_only")) parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open")) parser.add_argument('--shutdown-timeout', metavar='', dest='shutdown_timeout', default=0, help=strings._("help_shutdown_timeout")) From af2bef632df597382b0c540c351082d04cd81131 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 12 Nov 2017 11:47:52 +1100 Subject: [PATCH 16/22] Better wording for shutdown timer --- share/locale/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/en.json b/share/locale/en.json index 21282589..2847d33d 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -92,7 +92,7 @@ "gui_settings_button_cancel": "Cancel", "gui_settings_button_help": "Help", "gui_settings_shutdown_timeout_choice": "Set auto-stop timer?", - "gui_settings_shutdown_timeout": "Auto-stop share at:", + "gui_settings_shutdown_timeout": "Stop the share at:", "settings_saved": "Settings saved to {}", "settings_error_unknown": "Can't connect to Tor controller because the settings don't make sense.", "settings_error_automatic": "Can't connect to Tor controller. Is Tor Browser running in the background? If you don't have it you can get it from:\nhttps://www.torproject.org/.", From 454a6a638beab9d0db52aa786f0a57e259687222 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 2 Dec 2017 14:48:44 +1100 Subject: [PATCH 17/22] Always show the shutdown timer checkbox, in case the user changes their mind and wants to unset it --- onionshare_gui/server_status.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 452c2062..7746fdd2 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -106,14 +106,12 @@ class ServerStatus(QtWidgets.QVBoxLayout): if checked: self.timer_enabled = True # Hide the checkbox, show the options - self.server_shutdown_timeout_checkbox.hide() self.server_shutdown_timeout_label.show() # Reset the default timer to 5 minutes into the future after toggling the option on self.server_shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) self.server_shutdown_timeout.show() else: self.timer_enabled = False - self.server_shutdown_timeout_checkbox.show() self.server_shutdown_timeout_label.hide() self.server_shutdown_timeout.hide() @@ -164,22 +162,23 @@ class ServerStatus(QtWidgets.QVBoxLayout): self.server_button.setEnabled(True) self.server_button.setText(strings._('gui_start_server', True)) self.server_shutdown_timeout.setEnabled(True) - self.server_shutdown_timeout_checkbox.show() + self.server_shutdown_timeout_checkbox.setEnabled(True) self.server_shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Unchecked) elif self.status == self.STATUS_STARTED: self.server_button.setEnabled(True) self.server_button.setText(strings._('gui_stop_server', True)) self.server_shutdown_timeout.setEnabled(False) - self.server_shutdown_timeout_checkbox.hide() + self.server_shutdown_timeout_checkbox.setEnabled(False) elif self.status == self.STATUS_WORKING: self.server_button.setEnabled(False) self.server_button.setText(strings._('gui_please_wait')) self.server_shutdown_timeout.setEnabled(False) - self.server_shutdown_timeout_checkbox.hide() + self.server_shutdown_timeout_checkbox.setEnabled(False) else: self.server_button.setEnabled(False) self.server_button.setText(strings._('gui_please_wait')) self.server_shutdown_timeout.setEnabled(False) + self.server_shutdown_timeout_checkbox.setEnabled(False) def server_button_clicked(self): """ From 6ad2737d085071451446bb088db7ca2c92bb03b2 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 2 Dec 2017 14:53:38 +1100 Subject: [PATCH 18/22] Properly stop the server when the timeout is reached (and reset the interface so we can share again) --- onionshare_gui/onionshare_gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 508db655..e1d07ada 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -390,7 +390,7 @@ class OnionShareGui(QtWidgets.QMainWindow): if self.app.shutdown_timer and self.server_status.timer_enabled: if self.timeout > 0: if not self.app.shutdown_timer.is_alive(): - self.stop_server() + self.server_status.stop_server() self.status_bar.showMessage(strings._('close_on_timeout',True)) self.server_status.shutdown_timeout_reset() From 3b52f584a155ab91359d72bb6108f72c128fe179 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 3 Dec 2017 13:21:25 +1100 Subject: [PATCH 19/22] Don't auto-stop the share if a download is still in progress --- onionshare_gui/onionshare_gui.py | 32 ++++++++++++++++++++------------ share/locale/en.json | 1 + 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index e1d07ada..bdf0d105 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -384,24 +384,17 @@ class OnionShareGui(QtWidgets.QMainWindow): Check for messages communicated from the web app, and update the GUI accordingly. """ self.update() - - # If the auto-shutdown timer has stopped, stop the server - if self.server_status.status == self.server_status.STATUS_STARTED: - if self.app.shutdown_timer and self.server_status.timer_enabled: - if self.timeout > 0: - if not self.app.shutdown_timer.is_alive(): - self.server_status.stop_server() - self.status_bar.showMessage(strings._('close_on_timeout',True)) - self.server_status.shutdown_timeout_reset() + global download_in_progress + try: + download_in_progress + except NameError: + download_in_progress = False # scroll to the bottom of the dl progress bar log pane # if a new download has been added if self.new_download: self.vbar.setValue(self.vbar.maximum()) self.new_download = False - # only check for requests if the server is running - if self.server_status.status != self.server_status.STATUS_STARTED: - return events = [] @@ -430,9 +423,11 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == web.REQUEST_PROGRESS: self.downloads.update_download(event["data"]["id"], event["data"]["bytes"]) + download_in_progress = True # is the download complete? if event["data"]["bytes"] == web.zip_filesize: + download_in_progress = False if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True)) # close on finish? @@ -441,6 +436,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.server_status.shutdown_timeout_reset() elif event["type"] == web.REQUEST_CANCELED: + download_in_progress = False self.downloads.cancel_download(event["data"]["id"]) if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) @@ -448,6 +444,18 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["path"] != '/favicon.ico': self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(web.error404_count, strings._('other_page_loaded', True), event["path"])) + # If the auto-shutdown timer has stopped, stop the server + if self.server_status.status == self.server_status.STATUS_STARTED: + if self.app.shutdown_timer and self.server_status.timer_enabled: + if self.timeout > 0: + if not self.app.shutdown_timer.is_alive(): + if not download_in_progress: + self.server_status.stop_server() + self.status_bar.showMessage(strings._('close_on_timeout',True)) + self.server_status.shutdown_timeout_reset() + else: + self.status_bar.showMessage(strings._('timeout_download_still_running', True)) + def copy_url(self): """ When the URL gets copied to the clipboard, display this in the status bar. diff --git a/share/locale/en.json b/share/locale/en.json index 2847d33d..03a6a28e 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -14,6 +14,7 @@ "other_page_loaded": "URL loaded", "close_on_timeout": "Closing automatically because timeout was reached", "closing_automatically": "Closing automatically because download finished", + "timeout_download_still_running": "Waiting for download to complete before auto-stopping", "large_filesize": "Warning: Sending large files could take hours", "error_tails_invalid_port": "Invalid value, port must be an integer", "error_tails_unknown_root": "Unknown error with Tails root process", From 91a0c6018978bf234fe1675d8c99cd385444853f Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 4 Dec 2017 15:03:28 +1100 Subject: [PATCH 20/22] Better fix for preventing timeout firing if a download is not yet done (works for CLI as well as GUI) --- onionshare/__init__.py | 2 +- onionshare/web.py | 5 +++-- onionshare_gui/onionshare_gui.py | 11 ++--------- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 6ab030ef..4b7b47b0 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -134,7 +134,7 @@ def main(cwd=None): while t.is_alive(): if app.shutdown_timeout > 0: # if the shutdown timer was set and has run out, stop the server - if not app.shutdown_timer.is_alive(): + if not app.shutdown_timer.is_alive() and web.done: print(strings._("close_on_timeout")) web.stop(app.port) break diff --git a/onionshare/web.py b/onionshare/web.py index 4ae10221..03da8fd7 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -187,6 +187,7 @@ def check_slug_candidate(slug_candidate, slug_compare=None): # one download at a time. download_in_progress = False +done = False @app.route("/") def index(slug_candidate): @@ -236,7 +237,7 @@ def download(slug_candidate): # Deny new downloads if "Stop After First Download" is checked and there is # currently a download - global stay_open, download_in_progress + global stay_open, download_in_progress, done deny_download = not stay_open and download_in_progress if deny_download: r = make_response(render_template_string(open(common.get_resource_path('html/denied.html')).read())) @@ -267,7 +268,7 @@ def download(slug_candidate): client_cancel = False # Starting a new download - global stay_open, download_in_progress + global stay_open, download_in_progress, done if not stay_open: download_in_progress = True diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 2a3b3468..a9f800be 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -384,11 +384,6 @@ class OnionShareGui(QtWidgets.QMainWindow): Check for messages communicated from the web app, and update the GUI accordingly. """ self.update() - global download_in_progress - try: - download_in_progress - except NameError: - download_in_progress = False # scroll to the bottom of the dl progress bar log pane # if a new download has been added @@ -423,23 +418,21 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == web.REQUEST_PROGRESS: self.downloads.update_download(event["data"]["id"], event["data"]["bytes"]) - download_in_progress = True # is the download complete? if event["data"]["bytes"] == web.zip_filesize: - download_in_progress = False if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True)) # close on finish? if not web.get_stay_open(): self.server_status.stop_server() self.server_status.shutdown_timeout_reset() + self.status_bar.showMessage(strings._('closing_automatically',True)) else: if self.server_status.status == self.server_status.STATUS_STOPPED: self.downloads.cancel_download(event["data"]["id"]) elif event["type"] == web.REQUEST_CANCELED: - download_in_progress = False self.downloads.cancel_download(event["data"]["id"]) if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) @@ -452,7 +445,7 @@ class OnionShareGui(QtWidgets.QMainWindow): if self.app.shutdown_timer and self.server_status.timer_enabled: if self.timeout > 0: if not self.app.shutdown_timer.is_alive(): - if not download_in_progress: + if web.done: self.server_status.stop_server() self.status_bar.showMessage(strings._('close_on_timeout',True)) self.server_status.shutdown_timeout_reset() From 884d8389edcc392c144266a6e7f628918d91145e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 4 Dec 2017 15:22:46 +1100 Subject: [PATCH 21/22] Update comment to reflect that we don't hide the timer checkbox when checked anymore --- onionshare_gui/server_status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 7746fdd2..e0a944e3 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -101,7 +101,7 @@ class ServerStatus(QtWidgets.QVBoxLayout): def shutdown_timeout_toggled(self, checked): """ - Shutdown timer option was toggled. If checked, hide the option and show the timer settings. + Shutdown timer option was toggled. If checked, show the timer settings. """ if checked: self.timer_enabled = True From eaa37206e54b112012e3470480d883480df4a7b9 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 5 Dec 2017 11:18:26 +1100 Subject: [PATCH 22/22] Let the timer stop the share if there were no downloads, or all downloads are done --- onionshare/__init__.py | 10 ++++++---- onionshare_gui/onionshare_gui.py | 8 +++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 4b7b47b0..99beb0e0 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -134,10 +134,12 @@ def main(cwd=None): while t.is_alive(): if app.shutdown_timeout > 0: # if the shutdown timer was set and has run out, stop the server - if not app.shutdown_timer.is_alive() and web.done: - print(strings._("close_on_timeout")) - web.stop(app.port) - break + if not app.shutdown_timer.is_alive(): + # If there were no attempts to download the share, or all downloads are done, we can stop + if web.download_count == 0 or web.done: + print(strings._("close_on_timeout")) + web.stop(app.port) + break # Allow KeyboardInterrupt exception to be handled with threads # https://stackoverflow.com/questions/3788208/python-threading-ignores-keyboardinterrupt-exception time.sleep(0.2) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index a9f800be..fd278394 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -427,7 +427,7 @@ class OnionShareGui(QtWidgets.QMainWindow): if not web.get_stay_open(): self.server_status.stop_server() self.server_status.shutdown_timeout_reset() - self.status_bar.showMessage(strings._('closing_automatically',True)) + self.status_bar.showMessage(strings._('closing_automatically', True)) else: if self.server_status.status == self.server_status.STATUS_STOPPED: self.downloads.cancel_download(event["data"]["id"]) @@ -445,10 +445,12 @@ class OnionShareGui(QtWidgets.QMainWindow): if self.app.shutdown_timer and self.server_status.timer_enabled: if self.timeout > 0: if not self.app.shutdown_timer.is_alive(): - if web.done: + # If there were no attempts to download the share, or all downloads are done, we can stop + if web.download_count == 0 or web.done: self.server_status.stop_server() - self.status_bar.showMessage(strings._('close_on_timeout',True)) + self.status_bar.showMessage(strings._('close_on_timeout', True)) self.server_status.shutdown_timeout_reset() + # A download is probably still running - hold off on stopping the share else: self.status_bar.showMessage(strings._('timeout_download_still_running', True))