diff options
59 files changed, 629 insertions, 150 deletions
diff --git a/onionshare/__init__.py b/onionshare/__init__.py index cbfe222c..616cf9db 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -19,6 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. """ import os, sys, time, argparse, threading +from datetime import datetime +from datetime import timedelta from . import strings from .common import Common @@ -53,7 +55,8 @@ def main(cwd=None): 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='<int>', dest='shutdown_timeout', default=0, help=strings._("help_shutdown_timeout")) + parser.add_argument('--autostart-timer', metavar='<int>', dest='autostart_timer', default=0, help=strings._("help_autostart_timer")) + parser.add_argument('--autostop-timer', metavar='<int>', dest='autostop_timer', default=0, help=strings._("help_autostop_timer")) parser.add_argument('--connect-timeout', metavar='<int>', dest='connect_timeout', default=120, help=strings._("help_connect_timeout")) parser.add_argument('--stealth', action='store_true', dest='stealth', help=strings._("help_stealth")) parser.add_argument('--receive', action='store_true', dest='receive', help=strings._("help_receive")) @@ -69,7 +72,8 @@ def main(cwd=None): local_only = bool(args.local_only) debug = bool(args.debug) stay_open = bool(args.stay_open) - shutdown_timeout = int(args.shutdown_timeout) + autostart_timer = int(args.autostart_timer) + autostop_timer = int(args.autostop_timer) connect_timeout = int(args.connect_timeout) stealth = bool(args.stealth) receive = bool(args.receive) @@ -122,10 +126,51 @@ def main(cwd=None): # Start the onionshare app try: - app = OnionShare(common, onion, local_only, shutdown_timeout) + common.settings.load() + if not common.settings.get('public_mode'): + web.generate_slug(common.settings.get('slug')) + else: + web.slug = None + app = OnionShare(common, onion, local_only, autostop_timer) app.set_stealth(stealth) app.choose_port() - app.start_onion_service() + # Delay the startup if a startup timer was set + if autostart_timer > 0: + # Can't set a schedule that is later than the shutdown timer + if app.shutdown_timeout > 0 and app.shutdown_timeout < autostart_timer: + print(strings._('gui_timeout_cant_be_earlier_than_startup')) + sys.exit() + + app.start_onion_service(False, True) + if common.settings.get('public_mode'): + url = 'http://{0:s}'.format(app.onion_host) + else: + url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug) + schedule = datetime.now() + timedelta(seconds=autostart_timer) + if mode == 'receive': + print(strings._('receive_mode_data_dir').format(common.settings.get('data_dir'))) + print('') + print(strings._('receive_mode_warning')) + print('') + if stealth: + print(strings._("give_this_scheduled_url_receive_stealth").format(schedule.strftime("%b %d, %I:%M:%S%p"))) + print(app.auth_string) + else: + print(strings._("give_this_scheduled_url_receive").format(schedule.strftime("%b %d, %I:%M:%S%p"))) + else: + if stealth: + print(strings._("give_this_scheduled_url_share_stealth").format(schedule.strftime("%b %d, %I:%M:%S%p"))) + print(app.auth_string) + else: + print(strings._("give_this_scheduled_url_share").format(schedule.strftime("%b %d, %I:%M:%S%p"))) + print(url) + print('') + print(strings._("waiting_for_scheduled_time")) + app.onion.cleanup(False) + time.sleep(autostart_timer) + app.start_onion_service() + else: + app.start_onion_service() except KeyboardInterrupt: print("") sys.exit() @@ -151,7 +196,7 @@ def main(cwd=None): print('') # Start OnionShare http service in new thread - t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('public_mode'), common.settings.get('slug'))) + t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('public_mode'), web.slug)) t.daemon = True t.start() @@ -176,27 +221,30 @@ def main(cwd=None): url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug) print('') - if mode == 'receive': - print(strings._('receive_mode_data_dir').format(common.settings.get('data_dir'))) - print('') - print(strings._('receive_mode_warning')) - print('') - - if stealth: - print(strings._("give_this_url_receive_stealth")) - print(url) - print(app.auth_string) - else: - print(strings._("give_this_url_receive")) - print(url) + if autostart_timer > 0: + print(strings._('server_started')) else: - if stealth: - print(strings._("give_this_url_stealth")) - print(url) - print(app.auth_string) + if mode == 'receive': + print(strings._('receive_mode_data_dir').format(common.settings.get('data_dir'))) + print('') + print(strings._('receive_mode_warning')) + print('') + + if stealth: + print(strings._("give_this_url_receive_stealth")) + print(url) + print(app.auth_string) + else: + print(strings._("give_this_url_receive")) + print(url) else: - print(strings._("give_this_url")) - print(url) + if stealth: + print(strings._("give_this_url_stealth")) + print(url) + print(app.auth_string) + else: + print(strings._("give_this_url")) + print(url) print('') print(strings._("ctrlc_to_stop")) diff --git a/onionshare/onion.py b/onionshare/onion.py index 5cb11e27..51336df9 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -133,6 +133,8 @@ class Onion(object): self.stealth = False self.service_id = None + self.scheduled_key = None + self.scheduled_auth_cookie = None # Is bundled tor supported? if (self.common.platform == 'Windows' or self.common.platform == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False): @@ -425,27 +427,31 @@ class Onion(object): return False - def start_onion_service(self, port): + def start_onion_service(self, port, await_publication, save_scheduled_key=False): """ Start a onion service on port 80, pointing to the given port, and return the onion hostname. """ self.common.log('Onion', 'start_onion_service') - self.auth_string = None + if not self.supports_ephemeral: raise TorTooOld(strings._('error_ephemeral_not_supported')) if self.stealth and not self.supports_stealth: raise TorTooOld(strings._('error_stealth_not_supported')) - print(strings._("config_onion_service").format(int(port))) + if not save_scheduled_key: + print(strings._("config_onion_service").format(int(port))) if self.stealth: if self.settings.get('hidservauth_string'): hidservauth_string = self.settings.get('hidservauth_string').split()[2] basic_auth = {'onionshare':hidservauth_string} else: - basic_auth = {'onionshare':None} + if self.scheduled_auth_cookie: + basic_auth = {'onionshare':self.scheduled_auth_cookie} + else: + basic_auth = {'onionshare':None} else: basic_auth = None @@ -457,6 +463,14 @@ class Onion(object): # Assume it was a v3 key. Stem will throw an error if it's something illegible key_type = "ED25519-V3" + elif self.scheduled_key: + key_content = self.scheduled_key + if self.is_v2_key(key_content): + key_type = "RSA1024" + else: + # Assume it was a v3 key. Stem will throw an error if it's something illegible + key_type = "ED25519-V3" + else: key_type = "NEW" # Work out if we can support v3 onion services, which are preferred @@ -476,7 +490,6 @@ class Onion(object): if key_type == "NEW": debug_message += ', key_content={}'.format(key_content) self.common.log('Onion', 'start_onion_service', '{}'.format(debug_message)) - await_publication = True try: if basic_auth != None: res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=await_publication, basic_auth=basic_auth, key_type=key_type, key_content=key_content) @@ -495,6 +508,12 @@ class Onion(object): if not self.settings.get('private_key'): self.settings.set('private_key', res.private_key) + # If we were scheduling a future share, register the private key for later re-use + if save_scheduled_key: + self.scheduled_key = res.private_key + else: + self.scheduled_key = None + if self.stealth: # Similar to the PrivateKey, the Control port only returns the ClientAuth # in the response if it was responsible for creating the basic_auth password @@ -509,8 +528,19 @@ class Onion(object): self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie) self.settings.set('hidservauth_string', self.auth_string) else: - auth_cookie = list(res.client_auth.values())[0] - self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie) + if not self.scheduled_auth_cookie: + auth_cookie = list(res.client_auth.values())[0] + self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie) + if save_scheduled_key: + # Register the HidServAuth for the scheduled share + self.scheduled_auth_cookie = auth_cookie + else: + self.scheduled_auth_cookie = None + else: + self.auth_string = 'HidServAuth {} {}'.format(onion_host, self.scheduled_auth_cookie) + if not save_scheduled_key: + # We've used the scheduled share's HidServAuth. Reset it to None for future shares + self.scheduled_auth_cookie = None if onion_host is not None: self.settings.save() diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index 551b8314..6598b975 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -68,7 +68,7 @@ class OnionShare(object): except: raise OSError(strings._('no_available_port')) - def start_onion_service(self): + def start_onion_service(self, await_publication=True, save_scheduled_key=False): """ Start the onionshare onion service. """ @@ -84,7 +84,7 @@ class OnionShare(object): self.onion_host = '127.0.0.1:{0:d}'.format(self.port) return - self.onion_host = self.onion.start_onion_service(self.port) + self.onion_host = self.onion.start_onion_service(self.port, await_publication, save_scheduled_key) if self.stealth: self.auth_string = self.onion.auth_string diff --git a/onionshare/settings.py b/onionshare/settings.py index 68cbb857..d015c5ce 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -85,6 +85,7 @@ class Settings(object): 'auth_password': '', 'close_after_first_download': True, 'shutdown_timeout': False, + 'startup_timer': False, 'use_stealth': False, 'use_autoupdate': True, 'autoupdate_timestamp': None, diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 66ba5b24..7ce87108 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -228,13 +228,11 @@ class Web(object): pass self.running = False - def start(self, port, stay_open=False, public_mode=False, persistent_slug=None): + def start(self, port, stay_open=False, public_mode=False, slug=None): """ Start the flask web server. """ - self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, persistent_slug={}'.format(port, stay_open, public_mode, persistent_slug)) - if not public_mode: - self.generate_slug(persistent_slug) + self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, slug={}'.format(port, stay_open, public_mode, slug)) self.stay_open = stay_open @@ -264,7 +262,7 @@ class Web(object): self.stop_q.put(True) # Reset any slug that was in use - self.slug = '' + self.slug = None # To stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown if self.running: diff --git a/onionshare_gui/mode/__init__.py b/onionshare_gui/mode/__init__.py index 4fe335e7..f1184f2d 100644 --- a/onionshare_gui/mode/__init__.py +++ b/onionshare_gui/mode/__init__.py @@ -24,6 +24,7 @@ from onionshare.common import ShutdownTimer from ..server_status import ServerStatus from ..threads import OnionThread +from ..threads import StartupTimer 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,10 +92,41 @@ 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 this is a scheduled share, display the countdown til the share starts + if self.server_status.status == ServerStatus.STATUS_WORKING: + if self.server_status.scheduled_start: + now = QtCore.QDateTime.currentDateTime() + if self.server_status.local_only: + seconds_remaining = now.secsTo(self.server_status.startup_timer.dateTime()) + else: + seconds_remaining = now.secsTo(self.server_status.scheduled_start.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-shutdown 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'): @@ -101,7 +136,7 @@ class Mode(QtWidgets.QWidget): # 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)) + 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(): @@ -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.scheduled_start: + 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.scheduled_start: + self.common.log('Mode', 'start_server', 'Starting startup timer') + self.startup_thread = StartupTimer(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. @@ -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/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 27abf5e5..e57d8c83 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -228,7 +228,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.scheduled_start: + 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 +242,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.scheduled_start: + 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')) @@ -313,6 +319,12 @@ class OnionShareGui(QtWidgets.QMainWindow): 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 startup timer setting, ensure the widget is hidden. + if not self.common.settings.get('startup_timer'): + self.share_mode.server_status.scheduled_start = None + self.receive_mode.server_status.scheduled_start = None + self.share_mode.server_status.startup_timer_container.hide() + self.receive_mode.server_status.startup_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..6a2b883b 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -56,10 +56,36 @@ class ServerStatus(QtWidgets.QWidget): self.app = app self.web = None + self.scheduled_start = None self.local_only = local_only self.resizeEvent(None) + # Startup timer layout + self.startup_timer_label = QtWidgets.QLabel(strings._('gui_settings_startup_timer')) + self.startup_timer = QtWidgets.QDateTimeEdit() + self.startup_timer.setDisplayFormat("hh:mm A MMM d, yy") + if self.local_only: + # For testing + self.startup_timer.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(15)) + self.startup_timer.setMinimumDateTime(QtCore.QDateTime.currentDateTime()) + else: + # Set proposed timer to be 5 minutes into the future + self.startup_timer.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.startup_timer.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60)) + self.startup_timer.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection) + startup_timer_layout = QtWidgets.QHBoxLayout() + startup_timer_layout.addWidget(self.startup_timer_label) + startup_timer_layout.addWidget(self.startup_timer) + + # Startup timer container, so it can all be hidden and shown as a group + startup_timer_container_layout = QtWidgets.QVBoxLayout() + startup_timer_container_layout.addLayout(startup_timer_layout) + self.startup_timer_container = QtWidgets.QWidget() + self.startup_timer_container.setLayout(startup_timer_container_layout) + self.startup_timer_container.hide() + # Shutdown timeout layout self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout')) self.shutdown_timeout = QtWidgets.QDateTimeEdit() @@ -123,6 +149,7 @@ class ServerStatus(QtWidgets.QWidget): layout = QtWidgets.QVBoxLayout() layout.addWidget(self.server_button) layout.addLayout(url_layout) + layout.addWidget(self.startup_timer_container) layout.addWidget(self.shutdown_timeout_container) self.setLayout(layout) @@ -154,6 +181,13 @@ class ServerStatus(QtWidgets.QWidget): except: pass + def startup_timer_reset(self): + """ + Reset the timer in the UI after stopping a share + """ + self.startup_timer.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + if not self.local_only: + self.startup_timer.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60)) def shutdown_timeout_reset(self): """ @@ -163,50 +197,58 @@ class ServerStatus(QtWidgets.QWidget): if not self.local_only: self.shutdown_timeout.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('startup_timer'): + self.startup_timer_container.hide() + if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_container.hide() - - if self.app.stealth: - self.copy_hidservauth_button.show() - else: - self.copy_hidservauth_button.hide() else: self.url_description.hide() self.url.hide() @@ -227,6 +269,8 @@ class ServerStatus(QtWidgets.QWidget): else: self.server_button.setText(strings._('gui_receive_start_server')) self.server_button.setToolTip('') + if self.common.settings.get('startup_timer'): + self.startup_timer_container.show() if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_container.show() elif self.status == self.STATUS_STARTED: @@ -236,23 +280,28 @@ 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('startup_timer'): + self.startup_timer_container.hide() 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)) - + self.server_button.setToolTip(strings._('gui_stop_server_shutdown_timeout_tooltip').format(self.shutdown_timeout.dateTime().toString("H:mmAP, MMM dd, yy"))) 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.scheduled_start: + self.startup_timer_container.hide() + self.server_button.setToolTip(strings._('gui_start_server_startup_timer_tooltip').format(self.startup_timer.dateTime().toString("H:mmAP, MMM dd, yy"))) + else: + self.server_button.setText(strings._('gui_please_wait')) if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_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('startup_timer'): + self.startup_timer_container.hide() + self.server_button.setToolTip(strings._('gui_start_server_startup_timer_tooltip').format(self.startup_timer.dateTime().toString("H:mmAP, MMM dd, yy"))) if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_container.hide() @@ -261,6 +310,16 @@ class ServerStatus(QtWidgets.QWidget): Toggle starting or stopping the server. """ if self.status == self.STATUS_STOPPED: + can_start = True + if self.common.settings.get('startup_timer'): + if self.local_only: + self.scheduled_start = self.startup_timer.dateTime().toPyDateTime() + else: + self.scheduled_start = self.startup_timer.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.scheduled_start: + can_start = False + Alert(self.common, strings._('gui_server_startup_timer_expired'), QtWidgets.QMessageBox.Warning) if self.common.settings.get('shutdown_timeout'): if self.local_only: self.timeout = self.shutdown_timeout.dateTime().toPyDateTime() @@ -269,10 +328,13 @@ class ServerStatus(QtWidgets.QWidget): 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: + can_start = False Alert(self.common, strings._('gui_server_timeout_expired'), QtWidgets.QMessageBox.Warning) - else: - self.start_server() - else: + if self.common.settings.get('startup_timer'): + if self.timeout <= self.scheduled_start: + Alert(self.common, strings._('gui_timeout_cant_be_earlier_than_startup'), QtWidgets.QMessageBox.Warning) + can_start = False + if can_start: self.start_server() elif self.status == self.STATUS_STARTED: self.stop_server() @@ -302,6 +364,7 @@ class ServerStatus(QtWidgets.QWidget): Stop the server. """ self.status = self.STATUS_WORKING + self.startup_timer_reset() self.shutdown_timeout_reset() self.update() self.server_stopped.emit() @@ -312,6 +375,7 @@ class ServerStatus(QtWidgets.QWidget): """ self.common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup') self.status = self.STATUS_WORKING + self.startup_timer_reset() self.shutdown_timeout_reset() self.update() self.server_canceled.emit() diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 2933784c..58fb2244 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -71,6 +71,23 @@ class SettingsDialog(QtWidgets.QDialog): self.public_mode_widget = QtWidgets.QWidget() self.public_mode_widget.setLayout(public_mode_layout) + # Whether or not to use a startup ('auto-start') timer + self.startup_timer_checkbox = QtWidgets.QCheckBox() + self.startup_timer_checkbox.setCheckState(QtCore.Qt.Checked) + self.startup_timer_checkbox.setText(strings._("gui_settings_startup_timer_checkbox")) + startup_timer_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-the-Startup-Timer")) + startup_timer_label.setStyleSheet(self.common.css['settings_whats_this']) + startup_timer_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + startup_timer_label.setOpenExternalLinks(True) + startup_timer_label.setMinimumSize(public_mode_label.sizeHint()) + startup_timer_layout = QtWidgets.QHBoxLayout() + startup_timer_layout.addWidget(self.startup_timer_checkbox) + startup_timer_layout.addWidget(startup_timer_label) + startup_timer_layout.addStretch() + startup_timer_layout.setContentsMargins(0,0,0,0) + self.startup_timer_widget = QtWidgets.QWidget() + self.startup_timer_widget.setLayout(startup_timer_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) @@ -91,6 +108,7 @@ class SettingsDialog(QtWidgets.QDialog): # General settings layout general_group_layout = QtWidgets.QVBoxLayout() general_group_layout.addWidget(self.public_mode_widget) + general_group_layout.addWidget(self.startup_timer_widget) general_group_layout.addWidget(self.shutdown_timeout_widget) general_group = QtWidgets.QGroupBox(strings._("gui_settings_general_label")) general_group.setLayout(general_group_layout) @@ -488,6 +506,12 @@ class SettingsDialog(QtWidgets.QDialog): else: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Unchecked) + startup_timer = self.old_settings.get('startup_timer') + if startup_timer: + self.startup_timer_checkbox.setCheckState(QtCore.Qt.Checked) + else: + self.startup_timer_checkbox.setCheckState(QtCore.Qt.Unchecked) + shutdown_timeout = self.old_settings.get('shutdown_timeout') if shutdown_timeout: self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) @@ -932,6 +956,7 @@ 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('startup_timer', self.startup_timer_checkbox.isChecked()) settings.set('shutdown_timeout', self.shutdown_timeout_checkbox.isChecked()) # Complicated logic here to force v2 onion mode on or off depending on other settings diff --git a/onionshare_gui/threads.py b/onionshare_gui/threads.py index 3b05bebf..cb7447b5 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 StartupTimer(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(StartupTimer, self).__init__() + self.mode = mode + self.canceled = canceled + self.mode.common.log('StartupTimer', '__init__') + + # allow this thread to be terminated + self.setTerminationEnabled() + + def run(self): + now = QtCore.QDateTime.currentDateTime() + scheduled_start = now.secsTo(self.mode.server_status.scheduled_start) + try: + # Sleep until scheduled time + while scheduled_start > 0 and self.canceled == False: + time.sleep(0.1) + now = QtCore.QDateTime.currentDateTime() + scheduled_start = now.secsTo(self.mode.server_status.scheduled_start) + # 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/share/locale/am.json b/share/locale/am.json index d226d86e..56705ebf 100644 --- a/share/locale/am.json +++ b/share/locale/am.json @@ -26,7 +26,7 @@ "systray_upload_started_message": "", "help_local_only": "", "help_stay_open": "", - "help_shutdown_timeout": "", + "help_autostop_timer": "", "help_stealth": "", "help_receive": "", "help_debug": "", diff --git a/share/locale/ar.json b/share/locale/ar.json index 4d79b942..458ae7bb 100644 --- a/share/locale/ar.json +++ b/share/locale/ar.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "بدأ مستخدم رفع ملفات الى حاسوبك", "help_local_only": "لا تستخدم تور (فقط لغرض التطوير)", "help_stay_open": "استمر في المشاركة بعد اول تحميل", - "help_shutdown_timeout": "أوقف المشاركة بعد ثواني محددة", + "help_autostop_timer": "أوقف المشاركة بعد ثواني محددة", "help_stealth": "", "help_receive": "", "help_debug": "", diff --git a/share/locale/bg.json b/share/locale/bg.json index 256f27e0..65d091d1 100644 --- a/share/locale/bg.json +++ b/share/locale/bg.json @@ -26,7 +26,7 @@ "systray_upload_started_message": "Ползвател започна да ъплоудва файлове на компютъра Ви", "help_local_only": "Не използвайте Тор (само за разработване)", "help_stay_open": "Продължи споделянето след първото изтегляне", - "help_shutdown_timeout": "Спри споделянето след дадено количество секунди", + "help_autostop_timer": "Спри споделянето след дадено количество секунди", "help_stealth": "Използвай клиент авторизация (напреднал)", "help_receive": "Получаване на дялове вместо изпращане", "help_debug": "Протоколирай OnionShare грешки на stdout и уеб грешки на диск", diff --git a/share/locale/bn.json b/share/locale/bn.json index c2071d10..2ac65d47 100644 --- a/share/locale/bn.json +++ b/share/locale/bn.json @@ -26,7 +26,7 @@ "systray_upload_started_message": "", "help_local_only": "Tor ব্যবহার করবে না (শুধুমাত্র ডেভেলপারদের জন্য)", "help_stay_open": "ফাইলগুলো পাঠানো হয়ে গেলেও শেয়ার করা থামিও না", - "help_shutdown_timeout": "নির্দিষ্ট সেকেন্ডের পর শেয়ার করা বন্ধ করে দিও", + "help_autostop_timer": "নির্দিষ্ট সেকেন্ডের পর শেয়ার করা বন্ধ করে দিও", "help_stealth": "ক্লায়েন্ট অনুমোদন ব্যবহার করুন (উন্নততর)", "help_receive": "কোনকিছু শেয়ার না করে শুধু গ্রহণ করবে", "help_debug": "OnionShare-এর এররগুলো stdout-এ দেখাও, আর ওয়েব এররগুলো ডিস্কে লগ করো", diff --git a/share/locale/ca.json b/share/locale/ca.json index a59dae2a..65f12df4 100644 --- a/share/locale/ca.json +++ b/share/locale/ca.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "Algú ha començat a pujar arxius al teu ordinador", "help_local_only": "No facis servir Tor (només per a desenvolupament)", "help_stay_open": "Mantingues obert el servei després d'enviar els arxius", - "help_shutdown_timeout": "Deixa de compartir al cap de tants segons", + "help_autostop_timer": "Deixa de compartir al cap de tants segons", "help_stealth": "Fes servir autorització de client (avançat)", "help_receive": "Rep recursos en comptes d'enviar-los", "help_debug": "Envia els errors d'OnionShare a stdout i els errors web al disc", diff --git a/share/locale/da.json b/share/locale/da.json index cef7188a..50e65951 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -21,7 +21,7 @@ "systray_download_canceled_message": "Brugeren annullerede downloaden", "help_local_only": "Brug ikke Tor (kun til udvikling)", "help_stay_open": "Fortsæt deling efter filerne er blevet sendt", - "help_shutdown_timeout": "Stop deling efter et vist antal sekunder", + "help_autostop_timer": "Stop deling efter et vist antal sekunder", "help_stealth": "Brug klientautentifikation (avanceret)", "help_debug": "Log OnionShare-fejl til stdout, og webfejl til disk", "help_filename": "Liste over filer eller mapper som skal deles", diff --git a/share/locale/de.json b/share/locale/de.json index 4435798d..dbc00898 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -53,7 +53,7 @@ "close_on_timeout": "Angehalten da der auto-stop Timer abgelaufen ist", "systray_upload_started_title": "OnionShare Upload wurde gestartet", "systray_upload_started_message": "Ein Benutzer hat begonnen, Dateien auf deinen Computer hochzuladen", - "help_shutdown_timeout": "Den Server nach einer bestimmten Zeit anhalten (in Sekunden)", + "help_autostop_timer": "Den Server nach einer bestimmten Zeit anhalten (in Sekunden)", "help_receive": "Empfange Dateien anstatt sie zu senden", "gui_share_stop_server_shutdown_timeout": "Server stoppen (läuft noch {} Sekunden)", "gui_share_stop_server_shutdown_timeout_tooltip": "Zeit läuft in {} Sekunden ab", diff --git a/share/locale/el.json b/share/locale/el.json index 4157c592..e4e60b26 100644 --- a/share/locale/el.json +++ b/share/locale/el.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "Ένας/μια χρήστης/τρια ξεκίνησε να ανεβάζει αρχεία στον υπολογιστή σου", "help_local_only": "Να μην χρησιμοποιηθεί το Tor (μόνο για development)", "help_stay_open": "Να συνεχίσει ο διαμοιρασμός μετά την αποστολή των αρχείων", - "help_shutdown_timeout": "Να τερματιστεί ο διαμοιρασμός μετά από ένα συγκεκριμένο αριθμό δευτερολέπτων", + "help_autostop_timer": "Να τερματιστεί ο διαμοιρασμός μετά από ένα συγκεκριμένο αριθμό δευτερολέπτων", "help_stealth": "Κάντε χρήση εξουσιοδότησης πελάτη (Για προχωρημένους)", "help_receive": "Λάβετε διαμοιρασμένα αρχεία αντι να τα στέλνετε", "help_debug": "Κατέγραψε τα σφάλματα του OnionShare στο stdout (συνήθως οθόνη) και τα σφάλματα web στον δίσκο", diff --git a/share/locale/en.json b/share/locale/en.json index 9dd0648e..973028c8 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -5,6 +5,11 @@ "give_this_url_stealth": "Give this address and HidServAuth line to the recipient:", "give_this_url_receive": "Give this address to the sender:", "give_this_url_receive_stealth": "Give this address and HidServAuth to the sender:", + "give_this_scheduled_url_share": "Give this address to your recipient, and tell them it won't be accessible until: {}", + "give_this_scheduled_url_receive": "Give this address to your sender, and tell them it won't be accessible until: {}", + "give_this_scheduled_url_share_stealth": "Give this address and HidServAuth line to your recipient, and tell them it won't be accessible until: {}", + "give_this_scheduled_url_receive_stealth": "Give this address and HidServAuth lineto your sender, and tell them it won't be accessible until: {}", + "server_started": "Server started", "ctrlc_to_stop": "Press Ctrl+C to stop the server", "not_a_file": "{0:s} is not a valid file.", "not_a_readable_file": "{0:s} is not a readable file.", @@ -15,7 +20,8 @@ "large_filesize": "Warning: Sending a large share could take hours", "help_local_only": "Don't use Tor (only for development)", "help_stay_open": "Continue sharing after files have been sent", - "help_shutdown_timeout": "Stop sharing after a given amount of seconds", + "help_autostart_timer": "Schedule this share to start N seconds from now", + "help_autostop_timer": "Stop sharing after a given amount of seconds", "help_connect_timeout": "Give up connecting to Tor after a given amount of seconds (default: 120)", "help_stealth": "Use client authorization (advanced)", "help_receive": "Receive shares instead of sending them", @@ -30,12 +36,12 @@ "gui_choose_items": "Choose", "gui_share_start_server": "Start sharing", "gui_share_stop_server": "Stop sharing", - "gui_share_stop_server_shutdown_timeout": "Stop Sharing ({}s remaining)", - "gui_share_stop_server_shutdown_timeout_tooltip": "Auto-stop timer ends at {}", + "gui_share_stop_server_shutdown_timeout": "Stop Sharing ({})", + "gui_stop_server_shutdown_timeout_tooltip": "Auto-stop timer ends at {}", + "gui_start_server_startup_timer_tooltip": "Auto-start timer ends at {}", "gui_receive_start_server": "Start Receive Mode", "gui_receive_stop_server": "Stop Receive Mode", "gui_receive_stop_server_shutdown_timeout": "Stop Receive Mode ({}s remaining)", - "gui_receive_stop_server_shutdown_timeout_tooltip": "Auto-stop timer ends at {}", "gui_copy_url": "Copy Address", "gui_copy_hidservauth": "Copy HidServAuth", "gui_canceled": "Canceled", @@ -43,6 +49,7 @@ "gui_copied_url": "OnionShare address copied to clipboard", "gui_copied_hidservauth_title": "Copied HidServAuth", "gui_copied_hidservauth": "HidServAuth line copied to clipboard", + "gui_waiting_to_start": "Scheduled to start in {}. Click to cancel.", "gui_please_wait": "Starting… Click to cancel.", "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_quit_title": "Not so fast", @@ -95,6 +102,8 @@ "gui_settings_button_help": "Help", "gui_settings_shutdown_timeout_checkbox": "Use auto-stop timer", "gui_settings_shutdown_timeout": "Stop the share at:", + "gui_settings_startup_timer_checkbox": "Use auto-start timer", + "gui_settings_startup_timer": "Start the share at:", "settings_error_unknown": "Can't connect to Tor controller because your settings don't make sense.", "settings_error_automatic": "Could not connect to the Tor controller. Is Tor Browser (available from torproject.org) running in the background?", "settings_error_socket_port": "Can't connect to the Tor controller at {}:{}.", @@ -120,8 +129,10 @@ "gui_tor_connection_error_settings": "Try changing how OnionShare connects to the Tor network in the settings.", "gui_tor_connection_canceled": "Could not connect to Tor.\n\nEnsure you are connected to the Internet, then re-open OnionShare and set up its connection to Tor.", "gui_tor_connection_lost": "Disconnected from Tor.", - "gui_server_started_after_timeout": "The auto-stop timer ran out before the server started.\nPlease make a new share.", - "gui_server_timeout_expired": "The auto-stop timer already ran out.\nPlease update it to start sharing.", + "gui_server_started_after_timeout": "The auto-stop timer ran out before the server started. Please make a new share.", + "gui_server_timeout_expired": "The auto-stop timer already ran out. Please update it to start sharing.", + "gui_server_startup_timer_expired": "The scheduled time has already passed. Please update it to start sharing.", + "gui_timeout_cant_be_earlier_than_startup": "The auto-stop time can't be the same or earlier than the start-up time. Please update it to start sharing.", "share_via_onionshare": "OnionShare it", "gui_connect_to_tor_for_onion_settings": "Connect to Tor to see onion service settings", "gui_use_legacy_v2_onions_checkbox": "Use legacy addresses", @@ -134,9 +145,11 @@ "gui_url_label_onetime_and_persistent": "This share will not auto-stop.<br><br>Every subsequent share will reuse the address. (To use one-time addresses, turn off \"Use persistent address\" in the settings.)", "gui_status_indicator_share_stopped": "Ready to share", "gui_status_indicator_share_working": "Starting…", + "gui_status_indicator_share_scheduled": "Scheduled…", "gui_status_indicator_share_started": "Sharing", "gui_status_indicator_receive_stopped": "Ready to receive", "gui_status_indicator_receive_working": "Starting…", + "gui_status_indicator_receive_scheduled": "Scheduled…", "gui_status_indicator_receive_started": "Receiving", "gui_file_info": "{} files, {}", "gui_file_info_single": "{} file, {}", @@ -181,5 +194,10 @@ "gui_share_mode_no_files": "No Files Sent Yet", "gui_share_mode_timeout_waiting": "Waiting to finish sending", "gui_receive_mode_no_files": "No Files Received Yet", - "gui_receive_mode_timeout_waiting": "Waiting to finish receiving" + "gui_receive_mode_timeout_waiting": "Waiting to finish receiving", + "waiting_for_scheduled_time": "Waiting for the scheduled time before starting...", + "days_first_letter": "d", + "hours_first_letter": "h", + "minutes_first_letter": "m", + "seconds_first_letter": "s" } diff --git a/share/locale/es.json b/share/locale/es.json index 9ad6fad5..4bb5b839 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -24,7 +24,7 @@ "close_on_timeout": "Parado porque el temporizador expiró", "timeout_download_still_running": "Esperando a que se complete la descarga", "large_filesize": "Advertencia: Enviar un archivo tan grande podría llevar horas", - "help_shutdown_timeout": "Dejar de compartir después de una determinada cantidad de segundos", + "help_autostop_timer": "Dejar de compartir después de una determinada cantidad de segundos", "help_stealth": "Usar autorización de cliente (avanzada)", "help_config": "Ubicación del archivo de configuración JSON personalizado (opcional)", "gui_copied_url_title": "Dirección de OnionShare copiada", diff --git a/share/locale/fa.json b/share/locale/fa.json index 7e6c305c..302bc067 100644 --- a/share/locale/fa.json +++ b/share/locale/fa.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "یک کاربر شروع به آپلود فایل بر روی کامپیوتر شما کرده است", "help_local_only": "عدم استفاده از Tor (فقط برای توسعه)", "help_stay_open": "ادامه اشتراک گذاری پس از ارسال دانلود ها", - "help_shutdown_timeout": "توقف به اشتراک گذاری پس از میزان ثانیه ای مشخص", + "help_autostop_timer": "توقف به اشتراک گذاری پس از میزان ثانیه ای مشخص", "help_stealth": "استفاده از احراز هویت کلاینت (پیشرفته)", "help_receive": "دریافت اشتراک به جای ارسال آن", "help_debug": "لاگ کردن خطاهای OnionShare روی stdout، و خطاهای وب بر روی دیسک", diff --git a/share/locale/fr.json b/share/locale/fr.json index 6c6ffbb4..89c4efc7 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -62,7 +62,7 @@ "version_string": "OnionShare {0:s} | https://onionshare.org/", "zip_progress_bar_format": "Compression : %p%", "error_ephemeral_not_supported": "OnionShare exige au moins Tor 0.2.7.1 et python3-stem 1.4.0.", - "help_shutdown_timeout": "Arrêter le partage après un certain nombre de secondes", + "help_autostop_timer": "Arrêter le partage après un certain nombre de secondes", "gui_tor_connection_error_settings": "Essayez de modifier dans les paramètres la façon dont OnionShare se connecte au réseau Tor.", "no_available_port": "Impossible de trouver un port disponible pour démarrer le service oignon", "gui_share_stop_server_shutdown_timeout": "Arrêter le partage ({}s restantes)", diff --git a/share/locale/ga.json b/share/locale/ga.json index 114661d2..339a1b83 100644 --- a/share/locale/ga.json +++ b/share/locale/ga.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "Thosaigh úsáideoir ag uaslódáil comhad go dtí do ríomhaire", "help_local_only": "Ná húsáid Tor (tástáil amháin)", "help_stay_open": "Lean ort ag comhroinnt tar éis an chéad íoslódáil", - "help_shutdown_timeout": "Stop ag comhroinnt tar éis líon áirithe soicindí", + "help_autostop_timer": "Stop ag comhroinnt tar éis líon áirithe soicindí", "help_stealth": "Úsáid údarú cliaint (ardleibhéal)", "help_receive": "Glac le comhaid chomhroinnte in áit iad a sheoladh", "help_debug": "Déan tuairisc ar earráidí OnionShare ar stdout, agus earráidí Gréasáin ar an diosca", diff --git a/share/locale/gu.json b/share/locale/gu.json index 8ebfc5fb..48b5d8b2 100644 --- a/share/locale/gu.json +++ b/share/locale/gu.json @@ -26,7 +26,7 @@ "systray_upload_started_message": "", "help_local_only": "", "help_stay_open": "", - "help_shutdown_timeout": "", + "help_autostop_timer": "", "help_stealth": "", "help_receive": "", "help_debug": "", diff --git a/share/locale/he.json b/share/locale/he.json index 11c255f6..ed49ba55 100644 --- a/share/locale/he.json +++ b/share/locale/he.json @@ -26,7 +26,7 @@ "systray_upload_started_message": "", "help_local_only": "", "help_stay_open": "", - "help_shutdown_timeout": "", + "help_autostop_timer": "", "help_stealth": "", "help_receive": "", "help_debug": "", diff --git a/share/locale/hu.json b/share/locale/hu.json index 34cc480b..eba9798d 100644 --- a/share/locale/hu.json +++ b/share/locale/hu.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "", "help_local_only": "", "help_stay_open": "", - "help_shutdown_timeout": "", + "help_autostop_timer": "", "help_stealth": "", "help_receive": "", "help_debug": "", diff --git a/share/locale/id.json b/share/locale/id.json index 2657cd90..bc8dfad6 100644 --- a/share/locale/id.json +++ b/share/locale/id.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "", "help_local_only": "Tidak menggunakan Tor (hanya untuk pengembangan)", "help_stay_open": "Lanjutkan berbagi setelah berkas telah terkirim", - "help_shutdown_timeout": "Berhenti berbagi setelah beberapa detik", + "help_autostop_timer": "Berhenti berbagi setelah beberapa detik", "help_stealth": "Gunakan otorisasi klien (lanjutan)", "help_receive": "", "help_debug": "Catat kesalahan OnionShare ke stdout, dan kesalahan web ke disk", diff --git a/share/locale/is.json b/share/locale/is.json index 85b492ab..9109ec09 100644 --- a/share/locale/is.json +++ b/share/locale/is.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "", "help_local_only": "", "help_stay_open": "", - "help_shutdown_timeout": "", + "help_autostop_timer": "", "help_stealth": "", "help_receive": "", "help_debug": "", diff --git a/share/locale/it.json b/share/locale/it.json index f74f21c1..fee0f20a 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -39,7 +39,7 @@ "systray_download_canceled_message": "L'utente ha interrotto il download", "systray_upload_started_title": "Upload con OnionShare avviato", "systray_upload_started_message": "Un utente ha avviato l'upload di file sul tuo computer", - "help_shutdown_timeout": "Termina la condivisione dopo alcuni secondi", + "help_autostop_timer": "Termina la condivisione dopo alcuni secondi", "help_stealth": "Usa l'autorizzazione del client (avanzato)", "help_config": "Specifica il percorso del file di configurazione del JSON personalizzato", "gui_share_stop_server_shutdown_timeout": "Arresta la condivisione ({}s rimanenti)", diff --git a/share/locale/ja.json b/share/locale/ja.json index ad559ca2..f6846c52 100644 --- a/share/locale/ja.json +++ b/share/locale/ja.json @@ -26,7 +26,7 @@ "systray_upload_started_message": "ユーザーがファイルをアップロードし始めました", "help_local_only": "Torを使わない(開発利用のみ)", "help_stay_open": "ファイルが送信された後に共有し続けます", - "help_shutdown_timeout": "数秒後に共有が停止されます", + "help_autostop_timer": "数秒後に共有が停止されます", "help_stealth": "クライアント認証を使う(上級者向け)", "help_receive": "共有を送信する代わりに受信する", "help_debug": "OnionShareのエラーを標準出力に、Webのエラーをディスクに記録する", diff --git a/share/locale/ka.json b/share/locale/ka.json index 0f6541fa..72c24efd 100644 --- a/share/locale/ka.json +++ b/share/locale/ka.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "", "help_local_only": "", "help_stay_open": "", - "help_shutdown_timeout": "", + "help_autostop_timer": "", "help_stealth": "", "help_receive": "", "help_debug": "", diff --git a/share/locale/ko.json b/share/locale/ko.json index c5da4e9b..e766fa05 100644 --- a/share/locale/ko.json +++ b/share/locale/ko.json @@ -26,7 +26,7 @@ "systray_upload_started_message": "사용자가 파일들을 당신의 컴퓨터로 업로딩 하는것을 시작했습니다", "help_local_only": "Tor를 사용하지 마시오 (오직 개발자용)", "help_stay_open": "첫 다운로드 후 계속 공유하시오", - "help_shutdown_timeout": "정해진 초단위의 시간이 지난후 공유하는 것을 멈추시오", + "help_autostop_timer": "정해진 초단위의 시간이 지난후 공유하는 것을 멈추시오", "help_stealth": "고객 허가를 사용 (고급 수준의)", "help_receive": "그것들을 보내는것 대신 공유를 받으시오", "help_debug": "어니언쉐어 에러들은 표준 출력 장치로 접속하고, 웹 에러들은 디스크로 접속 ", diff --git a/share/locale/lg.json b/share/locale/lg.json index 25cd5c48..8c1819bd 100644 --- a/share/locale/lg.json +++ b/share/locale/lg.json @@ -26,7 +26,7 @@ "systray_upload_started_message": "", "help_local_only": "", "help_stay_open": "", - "help_shutdown_timeout": "", + "help_autostop_timer": "", "help_stealth": "", "help_receive": "", "help_debug": "", diff --git a/share/locale/mk.json b/share/locale/mk.json index 2273ba1e..6228bfaa 100644 --- a/share/locale/mk.json +++ b/share/locale/mk.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "", "help_local_only": "", "help_stay_open": "", - "help_shutdown_timeout": "", + "help_autostop_timer": "", "help_stealth": "", "help_receive": "", "help_debug": "", diff --git a/share/locale/nl.json b/share/locale/nl.json index 39c5ba9f..5ae039ff 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -21,7 +21,7 @@ "systray_download_canceled_message": "De gebruiker heeft de download afgebroken", "help_local_only": "Tor niet gebruiken (alleen voor ontwikkelingsdoeleinden)", "help_stay_open": "Blijven delen na afronden van eerste download", - "help_shutdown_timeout": "Stoppen met delen na het opgegeven aantal seconden", + "help_autostop_timer": "Stoppen met delen na het opgegeven aantal seconden", "help_stealth": "Client-authorisatie gebruiken (geavanceerd)", "help_debug": "Log OnionShare fouten naar stdout, en web fouten naar disk", "help_filename": "Lijst van bestanden of mappen om te delen", diff --git a/share/locale/no.json b/share/locale/no.json index f44375eb..4ef2721c 100644 --- a/share/locale/no.json +++ b/share/locale/no.json @@ -26,7 +26,7 @@ "systray_upload_started_message": "En bruker startet opplasting av filer til din datamaskin", "help_local_only": "Ikke bruk Tor (kun i utviklingsøyemed)", "help_stay_open": "Fortsett å dele etter at filene har blitt sendt", - "help_shutdown_timeout": "Stopp deling etter et gitt antall sekunder", + "help_autostop_timer": "Stopp deling etter et gitt antall sekunder", "help_stealth": "Bruk klientidentifisering (avansert)", "help_receive": "Motta delinger istedenfor å sende dem", "help_debug": "Log OnionShare-feil til stdout, og vev-feil til disk", diff --git a/share/locale/pa.json b/share/locale/pa.json index a2a967cc..4cfcf0e6 100644 --- a/share/locale/pa.json +++ b/share/locale/pa.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "", "help_local_only": "", "help_stay_open": "", - "help_shutdown_timeout": "", + "help_autostop_timer": "", "help_stealth": "", "help_receive": "", "help_debug": "", diff --git a/share/locale/pl.json b/share/locale/pl.json index f10a30ce..15cc3fc4 100644 --- a/share/locale/pl.json +++ b/share/locale/pl.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "Użytkownik rozpoczął wysyłanie plików na Twój komputer", "help_local_only": "Nie wykorzystuj sieci Tor (opcja zaawansowana)", "help_stay_open": "Kontynuuj udostępnianie po pierwszym pobraniu", - "help_shutdown_timeout": "Przestań udostępniać po określonym czasie w sekundach", + "help_autostop_timer": "Przestań udostępniać po określonym czasie w sekundach", "help_stealth": "Korzystaj z weryfikacji klienta (zaawansowane)", "help_receive": "Odbieraj dane zamiast je wysyłać", "help_debug": "Zapisz błędy OnionShare do stdout i zapisz błędy sieciowe na dysku", diff --git a/share/locale/pt_BR.json b/share/locale/pt_BR.json index 7723bf03..47030d22 100644 --- a/share/locale/pt_BR.json +++ b/share/locale/pt_BR.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "Alguém começou a carregar arquivos no seu computador", "help_local_only": "Não use Tor (unicamente para programação)", "help_stay_open": "Continuar a compartilhar após o envio de documentos", - "help_shutdown_timeout": "Parar de compartilhar após um número determinado de segundos", + "help_autostop_timer": "Parar de compartilhar após um número determinado de segundos", "help_stealth": "Usar autorização de cliente (avançado)", "help_receive": "Receber compartilhamentos ao invés de enviá-los", "help_debug": "Registrar erros do OnionShare no stdout e erros de rede, no disco", diff --git a/share/locale/pt_PT.json b/share/locale/pt_PT.json index 7085f2c2..e1c9fb54 100644 --- a/share/locale/pt_PT.json +++ b/share/locale/pt_PT.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "", "help_local_only": "", "help_stay_open": "", - "help_shutdown_timeout": "", + "help_autostop_timer": "", "help_stealth": "", "help_receive": "", "help_debug": "", diff --git a/share/locale/ro.json b/share/locale/ro.json index 54750c24..08446de1 100644 --- a/share/locale/ro.json +++ b/share/locale/ro.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "", "help_local_only": "", "help_stay_open": "", - "help_shutdown_timeout": "", + "help_autostop_timer": "", "help_stealth": "", "help_receive": "", "help_debug": "", diff --git a/share/locale/ru.json b/share/locale/ru.json index de372fe6..772e1e46 100644 --- a/share/locale/ru.json +++ b/share/locale/ru.json @@ -51,7 +51,7 @@ "systray_upload_started_message": "Пользователь начал загрузку файлов на Ваш компьютер", "help_local_only": "Не использовать Tor (только для разработки)", "help_stay_open": "Продолжить отправку после первого скачивания", - "help_shutdown_timeout": "Остановить отправку после заданного количества секунд", + "help_autostop_timer": "Остановить отправку после заданного количества секунд", "help_stealth": "Использовать авторизацию клиента (дополнительно)", "help_receive": "Получать загрузки вместо их отправки", "help_debug": "Направлять сообщения об ошибках OnionShare в stdout, ошибки сети сохранять на диск", diff --git a/share/locale/sl.json b/share/locale/sl.json index 42892ef9..bb79b972 100644 --- a/share/locale/sl.json +++ b/share/locale/sl.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "", "help_local_only": "", "help_stay_open": "", - "help_shutdown_timeout": "", + "help_autostop_timer": "", "help_stealth": "", "help_receive": "", "help_debug": "", diff --git a/share/locale/sn.json b/share/locale/sn.json index 11c255f6..ed49ba55 100644 --- a/share/locale/sn.json +++ b/share/locale/sn.json @@ -26,7 +26,7 @@ "systray_upload_started_message": "", "help_local_only": "", "help_stay_open": "", - "help_shutdown_timeout": "", + "help_autostop_timer": "", "help_stealth": "", "help_receive": "", "help_debug": "", diff --git a/share/locale/sv.json b/share/locale/sv.json index 3d3a16d6..53e05e43 100644 --- a/share/locale/sv.json +++ b/share/locale/sv.json @@ -26,7 +26,7 @@ "systray_upload_started_message": "En användare började ladda upp filer på din dator", "help_local_only": "Använd inte Tor (endast för utveckling)", "help_stay_open": "Fortsätt dela efter att filer har skickats", - "help_shutdown_timeout": "Sluta dela efter ett bestämt antal sekunder", + "help_autostop_timer": "Sluta dela efter ett bestämt antal sekunder", "help_stealth": "Använd klient-auktorisering (avancerat)", "help_receive": "Ta emot delningar istället för att skicka dem", "help_debug": "Logga OnionShare fel till stdout och webbfel till hårddisken", diff --git a/share/locale/wo.json b/share/locale/wo.json index a67a5f75..f39e1af7 100644 --- a/share/locale/wo.json +++ b/share/locale/wo.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "", "help_local_only": "", "help_stay_open": "", - "help_shutdown_timeout": "", + "help_autostop_timer": "", "help_stealth": "", "help_receive": "", "help_debug": "", diff --git a/share/locale/yo.json b/share/locale/yo.json index 25cd5c48..8c1819bd 100644 --- a/share/locale/yo.json +++ b/share/locale/yo.json @@ -26,7 +26,7 @@ "systray_upload_started_message": "", "help_local_only": "", "help_stay_open": "", - "help_shutdown_timeout": "", + "help_autostop_timer": "", "help_stealth": "", "help_receive": "", "help_debug": "", diff --git a/share/locale/zh_Hans.json b/share/locale/zh_Hans.json index cab1cdb6..31f25f08 100644 --- a/share/locale/zh_Hans.json +++ b/share/locale/zh_Hans.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "", "help_local_only": "不使用Tor(仅开发测试)", "help_stay_open": "文件传输完成后继续分享", - "help_shutdown_timeout": "超过给定时间(秒)后终止分享", + "help_autostop_timer": "超过给定时间(秒)后终止分享", "help_stealth": "使用服务端认证(高级选项)", "help_receive": "仅接收分享的文件,不发送", "help_debug": "将OnionShare错误日志记录到stdout,将web错误日志记录到磁盘", diff --git a/share/locale/zh_Hant.json b/share/locale/zh_Hant.json index bd7dd6d6..de34880c 100644 --- a/share/locale/zh_Hant.json +++ b/share/locale/zh_Hant.json @@ -25,7 +25,7 @@ "systray_upload_started_message": "", "help_local_only": "不要使用Tor(僅限開發使用)", "help_stay_open": "繼續分享即使檔案已傳送", - "help_shutdown_timeout": "在所給定的秒數後停止分享", + "help_autostop_timer": "在所給定的秒數後停止分享", "help_stealth": "使用客戶端認證 (進階選項)", "help_receive": "接收分享的檔案而不是傳送他們", "help_debug": "將OnionShare的錯誤日誌輸出到stdout, 並且將網路錯誤輸出到硬碟", diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index e4b3d4c9..9ff0477f 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -172,6 +172,9 @@ class GuiBaseTest(object): '''Test that the Server Status indicator shows we are Starting''' self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) + def server_status_indicator_says_scheduled(self, mode): + '''Test that the Server Status indicator shows we are Scheduled''' + self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_share_scheduled')) def server_is_started(self, mode, startup_time=2000): '''Test that the server has started''' @@ -294,7 +297,6 @@ class GuiBaseTest(object): mode.server_status.shutdown_timeout.setDateTime(timer) self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer) - def timeout_widget_hidden(self, mode): '''Test that the timeout widget is hidden when share has started''' self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible()) @@ -306,6 +308,37 @@ class GuiBaseTest(object): # We should have timed out now self.assertEqual(mode.server_status.status, 0) + # Startup timer tests + def set_startup_timer(self, mode, timer): + '''Test that the timer can be set''' + schedule = QtCore.QDateTime.currentDateTime().addSecs(timer) + mode.server_status.startup_timer.setDateTime(schedule) + self.assertTrue(mode.server_status.startup_timer.dateTime(), schedule) + + def startup_timer_widget_hidden(self, mode): + '''Test that the startup timer widget is hidden when share has started''' + self.assertFalse(mode.server_status.startup_timer_container.isVisible()) + + def scheduled_service_started(self, mode, wait): + '''Test that the server has timed out after the timer ran out''' + QtTest.QTest.qWait(wait) + # We should have started now + self.assertEqual(mode.server_status.status, 2) + + def cancel_the_share(self, mode): + '''Test that we can cancel a share before it's started up ''' + self.server_working_on_start_button_pressed(mode) + self.server_status_indicator_says_scheduled(mode) + self.add_delete_buttons_hidden() + self.settings_button_is_hidden() + self.set_startup_timer(mode, 10) + QtTest.QTest.mousePress(mode.server_status.server_button, QtCore.Qt.LeftButton) + QtTest.QTest.qWait(2000) + QtTest.QTest.mouseRelease(mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(mode.server_status.status, 0) + self.server_is_stopped(mode, False) + self.web_server_is_stopped() + # Hack to close an Alert dialog that would otherwise block tests def accept_dialog(self): window = self.gui.qtapp.activeWindow() diff --git a/tests/GuiShareTest.py b/tests/GuiShareTest.py index 716bab73..e8b31451 100644 --- a/tests/GuiShareTest.py +++ b/tests/GuiShareTest.py @@ -195,6 +195,25 @@ class GuiShareTest(GuiBaseTest): self.server_timed_out(self.gui.share_mode, 10000) self.web_server_is_stopped() + def run_all_share_mode_startup_timer_tests(self, public_mode): + """Auto-stop timer tests in share mode""" + self.run_all_share_mode_setup_tests() + self.set_startup_timer(self.gui.share_mode, 5) + self.server_working_on_start_button_pressed(self.gui.share_mode) + self.startup_timer_widget_hidden(self.gui.share_mode) + self.server_status_indicator_says_scheduled(self.gui.share_mode) + self.web_server_is_stopped() + self.scheduled_service_started(self.gui.share_mode, 7000) + self.web_server_is_running() + + def run_all_share_mode_startup_shutdown_mismatch_tests(self, public_mode): + """Auto-stop timer tests in share mode""" + self.run_all_share_mode_setup_tests() + self.set_startup_timer(self.gui.share_mode, 15) + self.set_timeout(self.gui.share_mode, 5) + QtCore.QTimer.singleShot(4000, self.accept_dialog) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.server_is_stopped(self.gui.share_mode, False) def run_all_share_mode_unreadable_file_tests(self): '''Attempt to share an unreadable file''' diff --git a/tests/TorGuiBaseTest.py b/tests/TorGuiBaseTest.py index e437ac93..8bd963bd 100644 --- a/tests/TorGuiBaseTest.py +++ b/tests/TorGuiBaseTest.py @@ -140,19 +140,6 @@ class TorGuiBaseTest(GuiBaseTest): else: self.assertEqual(clipboard.text(), 'http://{}/{}'.format(self.gui.app.onion_host, mode.server_status.web.slug)) - def cancel_the_share(self, mode): - '''Test that we can cancel this share before it's started up ''' - self.server_working_on_start_button_pressed(self.gui.share_mode) - self.server_status_indicator_says_starting(self.gui.share_mode) - self.add_delete_buttons_hidden() - self.settings_button_is_hidden() - QtTest.QTest.mousePress(mode.server_status.server_button, QtCore.Qt.LeftButton) - QtTest.QTest.qWait(1000) - QtTest.QTest.mouseRelease(mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEqual(mode.server_status.status, 0) - self.server_is_stopped(self.gui.share_mode, False) - self.web_server_is_stopped() - # Stealth tests def copy_have_hidserv_auth_button(self, mode): diff --git a/tests/local_onionshare_share_mode_autostart_timer_too_short_test.py b/tests/local_onionshare_share_mode_autostart_timer_too_short_test.py new file mode 100644 index 00000000..12343478 --- /dev/null +++ b/tests/local_onionshare_share_mode_autostart_timer_too_short_test.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +import pytest +import unittest +from PyQt5 import QtCore, QtTest + +from .GuiShareTest import GuiShareTest + +class LocalShareModeStartupTimerTooShortTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "startup_timer": True, + } + cls.gui = GuiShareTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + + @pytest.mark.gui + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_share_mode_setup_tests() + # Set a low timeout + self.set_startup_timer(self.gui.share_mode, 2) + QtTest.QTest.qWait(3000) + QtCore.QTimer.singleShot(4000, self.accept_dialog) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.share_mode.server_status.status, 0) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_share_mode_cancel_share_test.py b/tests/local_onionshare_share_mode_cancel_share_test.py new file mode 100644 index 00000000..24408730 --- /dev/null +++ b/tests/local_onionshare_share_mode_cancel_share_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModeCancelTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "startup_timer": True, + } + cls.gui = GuiShareTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + + @pytest.mark.gui + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_share_mode_setup_tests() + self.cancel_the_share(self.gui.share_mode) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_share_mode_startup_and_shutdown_timer_mismatch_test.py b/tests/local_onionshare_share_mode_startup_and_shutdown_timer_mismatch_test.py new file mode 100644 index 00000000..b3af8e17 --- /dev/null +++ b/tests/local_onionshare_share_mode_startup_and_shutdown_timer_mismatch_test.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModeStartupTimerTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "startup_timer": True, + "shutdown_timeout": True, + } + cls.gui = GuiShareTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + + @pytest.mark.gui + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_share_mode_startup_shutdown_mismatch_tests(False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_share_mode_startup_timer_test.py b/tests/local_onionshare_share_mode_startup_timer_test.py new file mode 100644 index 00000000..b5e08b10 --- /dev/null +++ b/tests/local_onionshare_share_mode_startup_timer_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModeStartupTimerTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "startup_timer": True, + } + cls.gui = GuiShareTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + + @pytest.mark.gui + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_share_mode_startup_timer_tests(False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/onionshare_share_mode_cancel_share_test.py b/tests/onionshare_share_mode_cancel_share_test.py index ed28ddd7..ed087a2c 100644 --- a/tests/onionshare_share_mode_cancel_share_test.py +++ b/tests/onionshare_share_mode_cancel_share_test.py @@ -8,6 +8,7 @@ class ShareModeCancelTest(unittest.TestCase, TorGuiShareTest): @classmethod def setUpClass(cls): test_settings = { + "startup_timer": True, } cls.gui = TorGuiShareTest.set_up(test_settings) diff --git a/tests/test_onionshare.py b/tests/test_onionshare.py index 7592a777..f141fed7 100644 --- a/tests/test_onionshare.py +++ b/tests/test_onionshare.py @@ -30,9 +30,10 @@ class MyOnion: self.auth_string = 'TestHidServAuth' self.private_key = '' self.stealth = stealth + self.scheduled_key = None @staticmethod - def start_onion_service(_): + def start_onion_service(self, await_publication=True, save_scheduled_key=False): return 'test_service_id.onion' diff --git a/tests/test_onionshare_settings.py b/tests/test_onionshare_settings.py index f4be2930..74ab4f8f 100644 --- a/tests/test_onionshare_settings.py +++ b/tests/test_onionshare_settings.py @@ -52,6 +52,7 @@ class TestSettings: 'auth_password': '', 'close_after_first_download': True, 'shutdown_timeout': False, + 'startup_timer': False, 'use_stealth': False, 'use_autoupdate': True, 'autoupdate_timestamp': None, |