summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--onionshare/__init__.py96
-rw-r--r--onionshare/onion.py44
-rw-r--r--onionshare/onionshare.py4
-rw-r--r--onionshare/settings.py1
-rw-r--r--onionshare/web/web.py8
-rw-r--r--onionshare_gui/mode/__init__.py86
-rw-r--r--onionshare_gui/onionshare_gui.py16
-rw-r--r--onionshare_gui/server_status.py138
-rw-r--r--onionshare_gui/settings_dialog.py25
-rw-r--r--onionshare_gui/threads.py69
-rw-r--r--share/locale/am.json2
-rw-r--r--share/locale/ar.json2
-rw-r--r--share/locale/bg.json2
-rw-r--r--share/locale/bn.json2
-rw-r--r--share/locale/ca.json2
-rw-r--r--share/locale/da.json2
-rw-r--r--share/locale/de.json2
-rw-r--r--share/locale/el.json2
-rw-r--r--share/locale/en.json32
-rw-r--r--share/locale/es.json2
-rw-r--r--share/locale/fa.json2
-rw-r--r--share/locale/fr.json2
-rw-r--r--share/locale/ga.json2
-rw-r--r--share/locale/gu.json2
-rw-r--r--share/locale/he.json2
-rw-r--r--share/locale/hu.json2
-rw-r--r--share/locale/id.json2
-rw-r--r--share/locale/is.json2
-rw-r--r--share/locale/it.json2
-rw-r--r--share/locale/ja.json2
-rw-r--r--share/locale/ka.json2
-rw-r--r--share/locale/ko.json2
-rw-r--r--share/locale/lg.json2
-rw-r--r--share/locale/mk.json2
-rw-r--r--share/locale/nl.json2
-rw-r--r--share/locale/no.json2
-rw-r--r--share/locale/pa.json2
-rw-r--r--share/locale/pl.json2
-rw-r--r--share/locale/pt_BR.json2
-rw-r--r--share/locale/pt_PT.json2
-rw-r--r--share/locale/ro.json2
-rw-r--r--share/locale/ru.json2
-rw-r--r--share/locale/sl.json2
-rw-r--r--share/locale/sn.json2
-rw-r--r--share/locale/sv.json2
-rw-r--r--share/locale/wo.json2
-rw-r--r--share/locale/yo.json2
-rw-r--r--share/locale/zh_Hans.json2
-rw-r--r--share/locale/zh_Hant.json2
-rw-r--r--tests/GuiBaseTest.py35
-rw-r--r--tests/GuiShareTest.py19
-rw-r--r--tests/TorGuiBaseTest.py13
-rw-r--r--tests/local_onionshare_share_mode_autostart_timer_too_short_test.py33
-rw-r--r--tests/local_onionshare_share_mode_cancel_share_test.py26
-rw-r--r--tests/local_onionshare_share_mode_startup_and_shutdown_timer_mismatch_test.py27
-rw-r--r--tests/local_onionshare_share_mode_startup_timer_test.py26
-rw-r--r--tests/onionshare_share_mode_cancel_share_test.py1
-rw-r--r--tests/test_onionshare.py3
-rw-r--r--tests/test_onionshare_settings.py1
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,