summaryrefslogtreecommitdiff
path: root/onionshare_gui
diff options
context:
space:
mode:
Diffstat (limited to 'onionshare_gui')
-rw-r--r--onionshare_gui/__init__.py31
-rw-r--r--onionshare_gui/mode/__init__.py126
-rw-r--r--onionshare_gui/mode/receive_mode/__init__.py14
-rw-r--r--onionshare_gui/mode/share_mode/__init__.py14
-rw-r--r--onionshare_gui/onionshare_gui.py32
-rw-r--r--onionshare_gui/server_status.py222
-rw-r--r--onionshare_gui/settings_dialog.py69
-rw-r--r--onionshare_gui/threads.py69
-rw-r--r--onionshare_gui/update_checker.py2
9 files changed, 401 insertions, 178 deletions
diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py
index 675bb52d..99c52937 100644
--- a/onionshare_gui/__init__.py
+++ b/onionshare_gui/__init__.py
@@ -26,7 +26,6 @@ import signal
from .widgets import Alert
from PyQt5 import QtCore, QtWidgets
-from onionshare import strings
from onionshare.common import Common
from onionshare.onion import Onion
from onionshare.onionshare import OnionShare
@@ -59,16 +58,8 @@ def main():
common = Common()
common.define_css()
- # Load the default settings and strings early, for the sake of being able to parse options.
- # These won't be in the user's chosen locale necessarily, but we need to parse them
- # early in order to even display the option to pass alternate settings (which might
- # contain a preferred locale).
- # If an alternate --config is passed, we'll reload strings later.
- common.load_settings()
- strings.load_strings(common)
-
# Display OnionShare banner
- print(strings._('version_string').format(common.version))
+ print("OnionShare {0:s} | https://onionshare.org/".format(common.version))
# Allow Ctrl-C to smoothly quit the program instead of throwing an exception
# https://stackoverflow.com/questions/42814093/how-to-handle-ctrlc-in-python-app-with-pyqt
@@ -80,10 +71,10 @@ def main():
# 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=strings._("help_local_only"))
- 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'))
+ 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
@@ -93,25 +84,23 @@ def main():
config = args.config
if config:
- # Re-load the strings, in case the provided config has changed locale
common.load_settings(config)
- strings.load_strings(common)
local_only = bool(args.local_only)
- debug = bool(args.debug)
+ verbose = bool(args.verbose)
- # Debug mode?
- common.debug = debug
+ # Verbose mode?
+ common.verbose = verbose
# Validation
if filenames:
valid = True
for filename in filenames:
if not os.path.isfile(filename) and not os.path.isdir(filename):
- Alert(common, strings._("not_a_file").format(filename))
+ Alert(common, "{0:s} is not a valid file.".format(filename))
valid = False
if not os.access(filename, os.R_OK):
- Alert(common, strings._("not_a_readable_file").format(filename))
+ Alert(common, "{0:s} is not a readable file.".format(filename))
valid = False
if not valid:
sys.exit()
diff --git a/onionshare_gui/mode/__init__.py b/onionshare_gui/mode/__init__.py
index 4fe335e7..8f5ff32b 100644
--- a/onionshare_gui/mode/__init__.py
+++ b/onionshare_gui/mode/__init__.py
@@ -20,10 +20,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
from PyQt5 import QtCore, QtWidgets, QtGui
from onionshare import strings
-from onionshare.common import ShutdownTimer
+from onionshare.common import AutoStopTimer
from ..server_status import ServerStatus
from ..threads import OnionThread
+from ..threads import AutoStartTimer
from ..widgets import Alert
class Mode(QtWidgets.QWidget):
@@ -35,6 +36,7 @@ class Mode(QtWidgets.QWidget):
starting_server_step2 = QtCore.pyqtSignal()
starting_server_step3 = QtCore.pyqtSignal()
starting_server_error = QtCore.pyqtSignal(str)
+ 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):
@@ -58,6 +60,7 @@ class Mode(QtWidgets.QWidget):
# Threads start out as None
self.onion_thread = None
self.web_thread = None
+ self.startup_thread = None
# Server status
self.server_status = ServerStatus(self.common, self.qtapp, self.app, None, self.local_only)
@@ -68,6 +71,7 @@ class Mode(QtWidgets.QWidget):
self.stop_server_finished.connect(self.server_status.stop_server_finished)
self.starting_server_step2.connect(self.start_server_step2)
self.starting_server_step3.connect(self.start_server_step3)
+ self.starting_server_early.connect(self.start_server_early)
self.starting_server_error.connect(self.start_server_error)
# Primary action
@@ -88,24 +92,55 @@ class Mode(QtWidgets.QWidget):
"""
pass
+ def human_friendly_time(self, secs):
+ """
+ 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
+ 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'))
+
+ return result
+
def timer_callback(self):
"""
This method is called regularly on a timer.
"""
- # If the auto-shutdown timer has stopped, stop the server
+ # If this is a scheduled share, display the countdown til the share starts
+ if self.server_status.status == ServerStatus.STATUS_WORKING:
+ 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())
+ else:
+ 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)))
+ else:
+ 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.shutdown_timer and self.common.settings.get('shutdown_timeout'):
- if self.timeout > 0:
+ 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.timeout)
+ seconds_remaining = now.secsTo(self.server_status.autostop_timer_datetime)
# Update the server button
- server_button_text = self.get_stop_server_shutdown_timeout_text()
- self.server_status.server_button.setText(server_button_text.format(seconds_remaining))
+ 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.status_bar.clearMessage()
- if not self.app.shutdown_timer.is_alive():
- if self.timeout_finished_should_stop_server():
+ if not self.app.autostop_timer_thread.is_alive():
+ if self.autostop_timer_finished_should_stop_server():
self.server_status.stop_server()
def timer_callback_custom(self):
@@ -114,15 +149,15 @@ class Mode(QtWidgets.QWidget):
"""
pass
- def get_stop_server_shutdown_timeout_text(self):
+ def get_stop_server_autostop_timer_text(self):
"""
- Return the string to put on the stop server button, if there's a shutdown timeout
+ Return the string to put on the stop server button, if there's an auto-stop timer
"""
pass
- def timeout_finished_should_stop_server(self):
+ def autostop_timer_finished_should_stop_server(self):
"""
- The shutdown timer expired, should we stop the server? Returns a bool
+ The auto-stop timer expired, should we stop the server? Returns a bool
"""
pass
@@ -142,7 +177,41 @@ class Mode(QtWidgets.QWidget):
self.status_bar.clearMessage()
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
+ # 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)
+ else:
+ self.start_onion_thread()
+
+ # 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.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)
+ self.startup_thread.error.connect(self.start_server_error)
+ self.startup_thread.canceled = False
+ self.startup_thread.start()
+
+ def start_onion_thread(self, obtain_onion_early=False):
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)
+ self.onion_thread.success_early.connect(self.starting_server_early.emit)
+ self.onion_thread.error.connect(self.starting_server_error.emit)
+ self.onion_thread.start()
+
+ def start_scheduled_service(self, obtain_onion_early=False):
+ # 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.onion_thread = OnionThread(self)
self.onion_thread.success.connect(self.starting_server_step2.emit)
self.onion_thread.error.connect(self.starting_server_error.emit)
@@ -154,6 +223,14 @@ class Mode(QtWidgets.QWidget):
"""
pass
+ def start_server_early(self):
+ """
+ An 'early' start of an onion service in order to obtain the onion
+ address for a scheduled start. Shows the onion address in the UI
+ in advance of actually starting the share.
+ """
+ self.server_status.show_url()
+
def start_server_step2(self):
"""
Step 2 in starting the onionshare server.
@@ -182,18 +259,18 @@ class Mode(QtWidgets.QWidget):
self.start_server_step3_custom()
- if self.common.settings.get('shutdown_timeout'):
+ if self.common.settings.get('autostop_timer'):
# Convert the date value to seconds between now and then
now = QtCore.QDateTime.currentDateTime()
- self.timeout = now.secsTo(self.server_status.timeout)
- # Set the shutdown timeout value
- if self.timeout > 0:
- self.app.shutdown_timer = ShutdownTimer(self.common, 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.
+ 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.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_timeout'))
+ self.start_server_error(strings._('gui_server_started_after_autostop_timer'))
def start_server_step3_custom(self):
"""
@@ -225,7 +302,12 @@ class Mode(QtWidgets.QWidget):
Cancel the server while it is preparing to start
"""
self.cancel_server_custom()
-
+ if self.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.onion_thread.quit()
diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py
index 5fb33ab3..4c0b49ba 100644
--- a/onionshare_gui/mode/receive_mode/__init__.py
+++ b/onionshare_gui/mode/receive_mode/__init__.py
@@ -86,24 +86,24 @@ class ReceiveMode(Mode):
self.wrapper_layout.addWidget(self.history, stretch=1)
self.setLayout(self.wrapper_layout)
- def get_stop_server_shutdown_timeout_text(self):
+ def get_stop_server_autostop_timer_text(self):
"""
- Return the string to put on the stop server button, if there's a shutdown timeout
+ Return the string to put on the stop server button, if there's an auto-stop timer
"""
- return strings._('gui_receive_stop_server_shutdown_timeout')
+ return strings._('gui_receive_stop_server_autostop_timer')
- def timeout_finished_should_stop_server(self):
+ def autostop_timer_finished_should_stop_server(self):
"""
- The shutdown timer expired, should we stop the server? Returns a bool
+ 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:
self.server_status.stop_server()
- self.server_status_label.setText(strings._('close_on_timeout'))
+ 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_timeout_waiting'))
+ self.server_status_label.setText(strings._('gui_receive_mode_autostop_timer_waiting'))
self.web.receive_mode.can_upload = False
return False
diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py
index 1f5ad00b..6cb50b2b 100644
--- a/onionshare_gui/mode/share_mode/__init__.py
+++ b/onionshare_gui/mode/share_mode/__init__.py
@@ -121,24 +121,24 @@ class ShareMode(Mode):
# Always start with focus on file selection
self.file_selection.setFocus()
- def get_stop_server_shutdown_timeout_text(self):
+ def get_stop_server_autostop_timer_text(self):
"""
- Return the string to put on the stop server button, if there's a shutdown timeout
+ Return the string to put on the stop server button, if there's an auto-stop timer
"""
- return strings._('gui_share_stop_server_shutdown_timeout')
+ return strings._('gui_share_stop_server_autostop_timer')
- def timeout_finished_should_stop_server(self):
+ def autostop_timer_finished_should_stop_server(self):
"""
- The shutdown timer expired, should we stop the server? Returns a bool
+ 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:
self.server_status.stop_server()
- self.server_status_label.setText(strings._('close_on_timeout'))
+ 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_timeout_waiting'))
+ self.server_status_label.setText(strings._('gui_share_mode_autostop_timer_waiting'))
return False
def start_server_custom(self):
diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py
index 27abf5e5..17839669 100644
--- a/onionshare_gui/onionshare_gui.py
+++ b/onionshare_gui/onionshare_gui.py
@@ -62,13 +62,17 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.config = config
if self.config:
self.common.load_settings(self.config)
+ else:
+ self.common.load_settings()
+
+ strings.load_strings(self.common)
# System tray
menu = QtWidgets.QMenu()
self.settings_action = menu.addAction(strings._('gui_settings_window_title'))
self.settings_action.triggered.connect(self.open_settings)
- help_action = menu.addAction(strings._('gui_settings_button_help'))
- help_action.triggered.connect(SettingsDialog.help_clicked)
+ 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.triggered.connect(self.close)
@@ -228,7 +232,10 @@ class OnionShareGui(QtWidgets.QMainWindow):
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_label.setText(strings._('gui_status_indicator_share_working'))
+ if self.share_mode.server_status.autostart_timer_datetime:
+ self.server_status_label.setText(strings._('gui_status_indicator_share_scheduled'))
+ else:
+ 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'))
@@ -239,7 +246,10 @@ class OnionShareGui(QtWidgets.QMainWindow):
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_label.setText(strings._('gui_status_indicator_receive_working'))
+ if self.receive_mode.server_status.autostart_timer_datetime:
+ self.server_status_label.setText(strings._('gui_status_indicator_receive_scheduled'))
+ else:
+ 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'))
@@ -309,10 +319,16 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.receive_mode.on_reload_settings()
self.status_bar.clearMessage()
- # If we switched off the shutdown timeout setting, ensure the widget is hidden.
- if not self.common.settings.get('shutdown_timeout'):
- self.share_mode.server_status.shutdown_timeout_container.hide()
- self.receive_mode.server_status.shutdown_timeout_container.hide()
+ # If we switched off the auto-stop timer setting, ensure the widget is hidden.
+ 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()
+ # If we switched off the auto-start timer setting, ensure the widget is hidden.
+ 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.share_mode.server_status.autostart_timer_container.hide()
+ self.receive_mode.server_status.autostart_timer_container.hide()
d = SettingsDialog(self.common, self.onion, self.qtapp, self.config, self.local_only)
d.settings_saved.connect(reload_settings)
diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py
index e34a3d16..0c51119e 100644
--- a/onionshare_gui/server_status.py
+++ b/onionshare_gui/server_status.py
@@ -56,34 +56,60 @@ class ServerStatus(QtWidgets.QWidget):
self.app = app
self.web = None
+ self.autostart_timer_datetime = None
self.local_only = local_only
self.resizeEvent(None)
- # Shutdown timeout layout
- self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout'))
- self.shutdown_timeout = QtWidgets.QDateTimeEdit()
- self.shutdown_timeout.setDisplayFormat("hh:mm A MMM d, yy")
+ # Auto-start timer layout
+ 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.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(15))
- self.shutdown_timeout.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 timeout to be 5 minutes into the future
- self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
+ # Set proposed timer to be 5 minutes into the future
+ 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.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60))
- self.shutdown_timeout.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection)
- shutdown_timeout_layout = QtWidgets.QHBoxLayout()
- shutdown_timeout_layout.addWidget(self.shutdown_timeout_label)
- shutdown_timeout_layout.addWidget(self.shutdown_timeout)
-
- # Shutdown timeout container, so it can all be hidden and shown as a group
- shutdown_timeout_container_layout = QtWidgets.QVBoxLayout()
- shutdown_timeout_container_layout.addLayout(shutdown_timeout_layout)
- self.shutdown_timeout_container = QtWidgets.QWidget()
- self.shutdown_timeout_container.setLayout(shutdown_timeout_container_layout)
- self.shutdown_timeout_container.hide()
+ 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)
+
+ # Auto-start timer container, so it can all be hidden and shown as a group
+ autostart_timer_container_layout = QtWidgets.QVBoxLayout()
+ autostart_timer_container_layout.addLayout(autostart_timer_layout)
+ self.autostart_timer_container = QtWidgets.QWidget()
+ self.autostart_timer_container.setLayout(autostart_timer_container_layout)
+ self.autostart_timer_container.hide()
+
+ # Auto-stop timer layout
+ 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())
+ else:
+ # Set proposed timer to be 5 minutes into the future
+ 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)
+ autostop_timer_layout = QtWidgets.QHBoxLayout()
+ autostop_timer_layout.addWidget(self.autostop_timer_label)
+ autostop_timer_layout.addWidget(self.autostop_timer_widget)
+
+ # Auto-stop timer container, so it can all be hidden and shown as a group
+ autostop_timer_container_layout = QtWidgets.QVBoxLayout()
+ autostop_timer_container_layout.addLayout(autostop_timer_layout)
+ self.autostop_timer_container = QtWidgets.QWidget()
+ self.autostop_timer_container.setLayout(autostop_timer_container_layout)
+ self.autostop_timer_container.hide()
# Server layout
self.server_button = QtWidgets.QPushButton()
@@ -123,7 +149,8 @@ class ServerStatus(QtWidgets.QWidget):
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.server_button)
layout.addLayout(url_layout)
- layout.addWidget(self.shutdown_timeout_container)
+ layout.addWidget(self.autostart_timer_container)
+ layout.addWidget(self.autostop_timer_container)
self.setLayout(layout)
def set_mode(self, share_mode, file_selection=None):
@@ -154,59 +181,74 @@ class ServerStatus(QtWidgets.QWidget):
except:
pass
+ def autostart_timer_reset(self):
+ """
+ Reset the auto-start timer in the UI after stopping a share
+ """
+ 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))
- def shutdown_timeout_reset(self):
+ def autostop_timer_reset(self):
"""
- Reset the timeout in the UI after stopping a share
+ Reset the auto-stop timer in the UI after stopping a share
"""
- self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
+ self.autostop_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
if not self.local_only:
- self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60))
+ self.autostop_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60))
- def update(self):
+ def show_url(self):
"""
- Update the GUI elements based on the current state.
+ Show the URL in the UI.
"""
- # Set the URL fields
- if self.status == self.STATUS_STARTED:
- self.url_description.show()
+ 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))
- else:
- self.url_description.setText(strings._('gui_receive_url_description').format(info_image))
+ if self.mode == ServerStatus.MODE_SHARE:
+ self.url_description.setText(strings._('gui_share_url_description').format(info_image))
+ else:
+ 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'))
- else:
- self.url_description.setToolTip(strings._('gui_url_label_persistent'))
+ # 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'))
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'))
- else:
- self.url_description.setToolTip(strings._('gui_url_label_stay_open'))
+ 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'))
+ else:
+ self.url_description.setToolTip(strings._('gui_url_label_stay_open'))
- self.url.setText(self.get_url())
- self.url.show()
+ self.url.setText(self.get_url())
+ self.url.show()
+ self.copy_url_button.show()
- self.copy_url_button.show()
+ if self.app.stealth:
+ self.copy_hidservauth_button.show()
+ else:
+ self.copy_hidservauth_button.hide()
+
+ def update(self):
+ """
+ Update the GUI elements based on the current state.
+ """
+ # Set the URL fields
+ if self.status == self.STATUS_STARTED:
+ 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)
self.common.settings.save()
- if self.common.settings.get('shutdown_timeout'):
- self.shutdown_timeout_container.hide()
+ if self.common.settings.get('autostart_timer'):
+ self.autostart_timer_container.hide()
- if self.app.stealth:
- self.copy_hidservauth_button.show()
- else:
- self.copy_hidservauth_button.hide()
+ if self.common.settings.get('autostop_timer'):
+ self.autostop_timer_container.hide()
else:
self.url_description.hide()
self.url.hide()
@@ -227,8 +269,10 @@ class ServerStatus(QtWidgets.QWidget):
else:
self.server_button.setText(strings._('gui_receive_start_server'))
self.server_button.setToolTip('')
- if self.common.settings.get('shutdown_timeout'):
- self.shutdown_timeout_container.show()
+ if self.common.settings.get('autostart_timer'):
+ self.autostart_timer_container.show()
+ 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.setEnabled(True)
@@ -236,43 +280,61 @@ class ServerStatus(QtWidgets.QWidget):
self.server_button.setText(strings._('gui_share_stop_server'))
else:
self.server_button.setText(strings._('gui_receive_stop_server'))
- if self.common.settings.get('shutdown_timeout'):
- self.shutdown_timeout_container.hide()
- if self.mode == ServerStatus.MODE_SHARE:
- self.server_button.setToolTip(strings._('gui_share_stop_server_shutdown_timeout_tooltip').format(self.timeout))
- else:
- self.server_button.setToolTip(strings._('gui_receive_stop_server_shutdown_timeout_tooltip').format(self.timeout))
-
+ if self.common.settings.get('autostart_timer'):
+ self.autostart_timer_container.hide()
+ 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")))
elif self.status == self.STATUS_WORKING:
self.server_button.setStyleSheet(self.common.css['server_status_button_working'])
self.server_button.setEnabled(True)
- self.server_button.setText(strings._('gui_please_wait'))
- if self.common.settings.get('shutdown_timeout'):
- self.shutdown_timeout_container.hide()
+ 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")))
+ else:
+ 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.setEnabled(False)
self.server_button.setText(strings._('gui_please_wait'))
- if self.common.settings.get('shutdown_timeout'):
- self.shutdown_timeout_container.hide()
+ 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.autostop_timer_container.hide()
def server_button_clicked(self):
"""
Toggle starting or stopping the server.
"""
if self.status == self.STATUS_STOPPED:
- if self.common.settings.get('shutdown_timeout'):
+ can_start = True
+ if self.common.settings.get('autostart_timer'):
if self.local_only:
- self.timeout = self.shutdown_timeout.dateTime().toPyDateTime()
+ self.autostart_timer_datetime = self.autostart_timer_widget.dateTime().toPyDateTime()
else:
- # 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.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(self.common, strings._('gui_server_timeout_expired'), QtWidgets.QMessageBox.Warning)
+ 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:
+ can_start = False
+ 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()
else:
- self.start_server()
- 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)
+ # 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:
+ can_start = False
+ 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)
+ can_start = False
+ if can_start:
self.start_server()
elif self.status == self.STATUS_STARTED:
self.stop_server()
@@ -302,7 +364,8 @@ class ServerStatus(QtWidgets.QWidget):
Stop the server.
"""
self.status = self.STATUS_WORKING
- self.shutdown_timeout_reset()
+ self.autostart_timer_reset()
+ self.autostop_timer_reset()
self.update()
self.server_stopped.emit()
@@ -312,7 +375,8 @@ class ServerStatus(QtWidgets.QWidget):
"""
self.common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup')
self.status = self.STATUS_WORKING
- self.shutdown_timeout_reset()
+ self.autostart_timer_reset()
+ self.autostop_timer_reset()
self.update()
self.server_canceled.emit()
diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py
index 2933784c..3c0b83f4 100644
--- a/onionshare_gui/settings_dialog.py
+++ b/onionshare_gui/settings_dialog.py
@@ -71,27 +71,45 @@ class SettingsDialog(QtWidgets.QDialog):
self.public_mode_widget = QtWidgets.QWidget()
self.public_mode_widget.setLayout(public_mode_layout)
- # Whether or not to use a shutdown ('auto-stop') timer
- self.shutdown_timeout_checkbox = QtWidgets.QCheckBox()
- self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked)
- self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox"))
- shutdown_timeout_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Stop-Timer"))
- shutdown_timeout_label.setStyleSheet(self.common.css['settings_whats_this'])
- shutdown_timeout_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
- shutdown_timeout_label.setOpenExternalLinks(True)
- shutdown_timeout_label.setMinimumSize(public_mode_label.sizeHint())
- shutdown_timeout_layout = QtWidgets.QHBoxLayout()
- shutdown_timeout_layout.addWidget(self.shutdown_timeout_checkbox)
- shutdown_timeout_layout.addWidget(shutdown_timeout_label)
- shutdown_timeout_layout.addStretch()
- shutdown_timeout_layout.setContentsMargins(0,0,0,0)
- self.shutdown_timeout_widget = QtWidgets.QWidget()
- self.shutdown_timeout_widget.setLayout(shutdown_timeout_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'])
+ autostart_timer_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
+ autostart_timer_label.setOpenExternalLinks(True)
+ autostart_timer_label.setMinimumSize(public_mode_label.sizeHint())
+ autostart_timer_layout = QtWidgets.QHBoxLayout()
+ 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)
+ 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'])
+ autostop_timer_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
+ autostop_timer_label.setOpenExternalLinks(True)
+ autostop_timer_label.setMinimumSize(public_mode_label.sizeHint())
+ autostop_timer_layout = QtWidgets.QHBoxLayout()
+ 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)
+ self.autostop_timer_widget = QtWidgets.QWidget()
+ self.autostop_timer_widget.setLayout(autostop_timer_layout)
# General settings layout
general_group_layout = QtWidgets.QVBoxLayout()
general_group_layout.addWidget(self.public_mode_widget)
- general_group_layout.addWidget(self.shutdown_timeout_widget)
+ general_group_layout.addWidget(self.autostart_timer_widget)
+ general_group_layout.addWidget(self.autostop_timer_widget)
general_group = QtWidgets.QGroupBox(strings._("gui_settings_general_label"))
general_group.setLayout(general_group_layout)
@@ -488,11 +506,17 @@ class SettingsDialog(QtWidgets.QDialog):
else:
self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Unchecked)
- shutdown_timeout = self.old_settings.get('shutdown_timeout')
- if shutdown_timeout:
- self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked)
+ autostart_timer = self.old_settings.get('autostart_timer')
+ if autostart_timer:
+ self.autostart_timer_checkbox.setCheckState(QtCore.Qt.Checked)
else:
- self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Unchecked)
+ self.autostart_timer_checkbox.setCheckState(QtCore.Qt.Unchecked)
+
+ 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')
if save_private_key:
@@ -932,7 +956,8 @@ class SettingsDialog(QtWidgets.QDialog):
settings.load() # To get the last update timestamp
settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked())
- settings.set('shutdown_timeout', self.shutdown_timeout_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():
diff --git a/onionshare_gui/threads.py b/onionshare_gui/threads.py
index 3b05bebf..26a9ee6b 100644
--- a/onionshare_gui/threads.py
+++ b/onionshare_gui/threads.py
@@ -28,6 +28,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)
def __init__(self, mode):
@@ -41,18 +42,30 @@ class OnionThread(QtCore.QThread):
def run(self):
self.mode.common.log('OnionThread', 'run')
+ # 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')
-
- # start onionshare http service in new thread
- self.mode.web_thread = WebThread(self.mode)
- self.mode.web_thread.start()
-
- # wait for modules in thread to load, preventing a thread-related cx_Freeze crash
- time.sleep(0.2)
+ 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'))
try:
- self.mode.app.start_onion_service()
- self.success.emit()
+ if self.mode.obtain_onion_early:
+ 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()
+ # Unregister the onion so we can use it in the next OnionThread
+ self.mode.app.onion.cleanup(False)
+ else:
+ self.mode.app.start_onion_service(await_publication=True)
+ # wait for modules in thread to load, preventing a thread-related cx_Freeze crash
+ time.sleep(0.2)
+ # start onionshare http service in new thread
+ self.mode.web_thread = WebThread(self.mode)
+ self.mode.web_thread.start()
+ self.success.emit()
except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e:
self.error.emit(e.args[0])
@@ -73,5 +86,39 @@ class WebThread(QtCore.QThread):
def run(self):
self.mode.common.log('WebThread', 'run')
- self.mode.app.choose_port()
- self.mode.web.start(self.mode.app.port, self.mode.app.stay_open, self.mode.common.settings.get('public_mode'), self.mode.common.settings.get('slug'))
+ 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.success.emit()
+
+
+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__')
+
+ # 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)
+ 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)
+ # 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.success.emit()
+ except ValueError as e:
+ self.error.emit(e.args[0])
+ return
diff --git a/onionshare_gui/update_checker.py b/onionshare_gui/update_checker.py
index a7e0b99c..1e37b73a 100644
--- a/onionshare_gui/update_checker.py
+++ b/onionshare_gui/update_checker.py
@@ -25,7 +25,7 @@ from distutils.version import LooseVersion as Version
from onionshare.settings import Settings
from onionshare.onion import Onion
-from . import strings
+from onionshare import strings
class UpdateCheckerCheckError(Exception):
"""