Merge pull request #800 from micahflee/690_language_dropdown

Add language dropdown
This commit is contained in:
Miguel Jacq 2018-10-11 13:07:02 +11:00 committed by GitHub
commit 21f5f21671
30 changed files with 848 additions and 567 deletions

View File

@ -33,7 +33,15 @@ def main(cwd=None):
""" """
common = Common() common = Common()
# Load the default settings and strings early, for the sake of being able to parse options.
# These won't be in the user's chosen locale necessarily, but we need to parse them
# early in order to even display the option to pass alternate settings (which might
# contain a preferred locale).
# If an alternate --config is passed, we'll reload strings later.
common.load_settings()
strings.load_strings(common) strings.load_strings(common)
# Display OnionShare banner
print(strings._('version_string').format(common.version)) print(strings._('version_string').format(common.version))
# OnionShare CLI in OSX needs to change current working directory (#132) # OnionShare CLI in OSX needs to change current working directory (#132)
@ -88,8 +96,11 @@ def main(cwd=None):
if not valid: if not valid:
sys.exit() sys.exit()
# Load settings # Re-load settings, if a custom config was passed in
if config:
common.load_settings(config) common.load_settings(config)
# Re-load the strings, in case the provided config has changed locale
strings.load_strings(common)
# Debug mode? # Debug mode?
common.debug = debug common.debug = debug

View File

@ -247,7 +247,7 @@ class Onion(object):
self.c = Controller.from_socket_file(path=self.tor_control_socket) self.c = Controller.from_socket_file(path=self.tor_control_socket)
self.c.authenticate() self.c.authenticate()
except Exception as e: except Exception as e:
raise BundledTorBroken(strings._('settings_error_bundled_tor_broken', True).format(e.args[0])) raise BundledTorBroken(strings._('settings_error_bundled_tor_broken').format(e.args[0]))
while True: while True:
try: try:

View File

@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import json import json
import os import os
import platform import platform
import locale
from . import strings from . import strings
@ -47,6 +48,25 @@ class Settings(object):
else: else:
self.common.log('Settings', '__init__', 'Supplied config does not exist or is unreadable. Falling back to default location') self.common.log('Settings', '__init__', 'Supplied config does not exist or is unreadable. Falling back to default location')
# Dictionary of available languages in this version of OnionShare,
# mapped to the language name, in that language
self.available_locales = {
'cs': 'Hrvatski', # Croatian
'da': 'Dansk', # Danish
'de': 'Deutsch', # German
'en': 'English', # English
'eo': 'Esperanto', # Esperanto
'es': 'Español', # Spanish
'fi': 'Suomi', # Finnish
'fr': 'Français', # French
'it': 'Italiano', # Italian
'nl': 'Nederlands', # Dutch
'no': 'Norsk', # Norweigan
'pt': 'Português', # Portuguese
'ru': 'Русский', # Russian
'tr': 'Türkçe' # Turkish
}
# These are the default settings. They will get overwritten when loading from disk # These are the default settings. They will get overwritten when loading from disk
self.default_settings = { self.default_settings = {
'version': self.common.version, 'version': self.common.version,
@ -74,7 +94,8 @@ class Settings(object):
'slug': '', 'slug': '',
'hidservauth_string': '', 'hidservauth_string': '',
'downloads_dir': self.build_default_downloads_dir(), 'downloads_dir': self.build_default_downloads_dir(),
'receive_allow_receiver_shutdown': True 'receive_allow_receiver_shutdown': True,
'locale': None # this gets defined in fill_in_defaults()
} }
self._settings = {} self._settings = {}
self.fill_in_defaults() self.fill_in_defaults()
@ -88,14 +109,26 @@ class Settings(object):
if key not in self._settings: if key not in self._settings:
self._settings[key] = self.default_settings[key] self._settings[key] = self.default_settings[key]
# Choose the default locale based on the OS preference, and fall-back to English
if self._settings['locale'] is None:
default_locale = locale.getdefaultlocale()[0][:2]
if default_locale not in self.available_locales:
default_locale = 'en'
self._settings['locale'] = default_locale
def build_filename(self): def build_filename(self):
""" """
Returns the path of the settings file. Returns the path of the settings file.
""" """
p = platform.system() p = platform.system()
if p == 'Windows': if p == 'Windows':
try:
appdata = os.environ['APPDATA'] appdata = os.environ['APPDATA']
return '{}\\OnionShare\\onionshare.json'.format(appdata) return '{}\\OnionShare\\onionshare.json'.format(appdata)
except:
# If for some reason we don't have the 'APPDATA' environment variable
# (like running tests in Linux while pretending to be in Windows)
return os.path.expanduser('~/.config/onionshare/onionshare.json')
elif p == 'Darwin': elif p == 'Darwin':
return os.path.expanduser('~/Library/Application Support/OnionShare/onionshare.json') return os.path.expanduser('~/Library/Application Support/OnionShare/onionshare.json')
else: else:
@ -136,7 +169,7 @@ class Settings(object):
except: except:
pass pass
open(self.filename, 'w').write(json.dumps(self._settings)) open(self.filename, 'w').write(json.dumps(self._settings))
print(strings._('settings_saved').format(self.filename)) self.common.log('Settings', 'save', 'Settings saved in {}'.format(self.filename))
def get(self, key): def get(self, key):
return self._settings[key] return self._settings[key]

View File

@ -22,39 +22,36 @@ import locale
import os import os
strings = {} strings = {}
translations = {}
def load_strings(common, default="en"): def load_strings(common):
""" """
Loads translated strings and fallback to English Loads translated strings and fallback to English
if the translation does not exist. if the translation does not exist.
""" """
global strings global strings, translations
# find locale dir # Load all translations
locale_dir = common.get_resource_path('locale')
# load all translations
translations = {} translations = {}
for filename in os.listdir(locale_dir): for locale in common.settings.available_locales:
abs_filename = os.path.join(locale_dir, filename) locale_dir = common.get_resource_path('locale')
lang, ext = os.path.splitext(filename) filename = os.path.join(locale_dir, "{}.json".format(locale))
if ext == '.json': with open(filename, encoding='utf-8') as f:
with open(abs_filename, encoding='utf-8') as f: translations[locale] = json.load(f)
translations[lang] = json.load(f)
strings = translations[default] # Build strings
lc, enc = locale.getdefaultlocale() default_locale = 'en'
if lc: current_locale = common.settings.get('locale')
lang = lc[:2] strings = {}
if lang in translations: for s in translations[default_locale]:
# if a string doesn't exist, fallback to English if s in translations[current_locale]:
for key in translations[default]: strings[s] = translations[current_locale][s]
if key in translations[lang]: else:
strings[key] = translations[lang][key] strings[s] = translations[default_locale][s]
def translated(k, gui=False): def translated(k):
""" """
Returns a translated string. Returns a translated string.
""" """

View File

@ -59,7 +59,15 @@ def main():
common = Common() common = Common()
common.define_css() common.define_css()
# Load the default settings and strings early, for the sake of being able to parse options.
# These won't be in the user's chosen locale necessarily, but we need to parse them
# early in order to even display the option to pass alternate settings (which might
# contain a preferred locale).
# If an alternate --config is passed, we'll reload strings later.
common.load_settings()
strings.load_strings(common) strings.load_strings(common)
# Display OnionShare banner
print(strings._('version_string').format(common.version)) print(strings._('version_string').format(common.version))
# Allow Ctrl-C to smoothly quit the program instead of throwing an exception # Allow Ctrl-C to smoothly quit the program instead of throwing an exception
@ -84,6 +92,10 @@ def main():
filenames[i] = os.path.abspath(filenames[i]) filenames[i] = os.path.abspath(filenames[i])
config = args.config config = args.config
if config:
# Re-load the strings, in case the provided config has changed locale
common.load_settings(config)
strings.load_strings(common)
local_only = bool(args.local_only) local_only = bool(args.local_only)
debug = bool(args.debug) debug = bool(args.debug)
@ -96,10 +108,10 @@ def main():
valid = True valid = True
for filename in filenames: for filename in filenames:
if not os.path.isfile(filename) and not os.path.isdir(filename): if not os.path.isfile(filename) and not os.path.isdir(filename):
Alert(common, strings._("not_a_file", True).format(filename)) Alert(common, strings._("not_a_file").format(filename))
valid = False valid = False
if not os.access(filename, os.R_OK): if not os.access(filename, os.R_OK):
Alert(common, strings._("not_a_readable_file", True).format(filename)) Alert(common, strings._("not_a_readable_file").format(filename))
valid = False valid = False
if not valid: if not valid:
sys.exit() sys.exit()

View File

@ -196,7 +196,7 @@ class UploadHistoryItem(HistoryItem):
self.started = datetime.now() self.started = datetime.now()
# Label # Label
self.label = QtWidgets.QLabel(strings._('gui_upload_in_progress', True).format(self.started.strftime("%b %d, %I:%M%p"))) self.label = QtWidgets.QLabel(strings._('gui_upload_in_progress').format(self.started.strftime("%b %d, %I:%M%p")))
# Progress bar # Progress bar
self.progress_bar = QtWidgets.QProgressBar() self.progress_bar = QtWidgets.QProgressBar()
@ -274,16 +274,16 @@ class UploadHistoryItem(HistoryItem):
self.ended = self.started = datetime.now() self.ended = self.started = datetime.now()
if self.started.year == self.ended.year and self.started.month == self.ended.month and self.started.day == self.ended.day: if self.started.year == self.ended.year and self.started.month == self.ended.month and self.started.day == self.ended.day:
if self.started.hour == self.ended.hour and self.started.minute == self.ended.minute: if self.started.hour == self.ended.hour and self.started.minute == self.ended.minute:
text = strings._('gui_upload_finished', True).format( text = strings._('gui_upload_finished').format(
self.started.strftime("%b %d, %I:%M%p") self.started.strftime("%b %d, %I:%M%p")
) )
else: else:
text = strings._('gui_upload_finished_range', True).format( text = strings._('gui_upload_finished_range').format(
self.started.strftime("%b %d, %I:%M%p"), self.started.strftime("%b %d, %I:%M%p"),
self.ended.strftime("%I:%M%p") self.ended.strftime("%I:%M%p")
) )
else: else:
text = strings._('gui_upload_finished_range', True).format( text = strings._('gui_upload_finished_range').format(
self.started.strftime("%b %d, %I:%M%p"), self.started.strftime("%b %d, %I:%M%p"),
self.ended.strftime("%b %d, %I:%M%p") self.ended.strftime("%b %d, %I:%M%p")
) )
@ -380,7 +380,7 @@ class History(QtWidgets.QWidget):
# Header # Header
self.header_label = QtWidgets.QLabel(header_text) self.header_label = QtWidgets.QLabel(header_text)
self.header_label.setStyleSheet(self.common.css['downloads_uploads_label']) self.header_label.setStyleSheet(self.common.css['downloads_uploads_label'])
clear_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) clear_button = QtWidgets.QPushButton(strings._('gui_clear_history'))
clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) clear_button.setStyleSheet(self.common.css['downloads_uploads_clear'])
clear_button.setFlat(True) clear_button.setFlat(True)
clear_button.clicked.connect(self.reset) clear_button.clicked.connect(self.reset)
@ -486,7 +486,7 @@ class History(QtWidgets.QWidget):
else: else:
image = self.common.get_resource_path('images/share_in_progress.png') image = self.common.get_resource_path('images/share_in_progress.png')
self.in_progress_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.in_progress_count)) self.in_progress_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.in_progress_count))
self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip', True).format(self.in_progress_count)) self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip').format(self.in_progress_count))
class ToggleHistory(QtWidgets.QPushButton): class ToggleHistory(QtWidgets.QPushButton):

View File

@ -63,7 +63,7 @@ class ReceiveMode(Mode):
) )
# Receive mode warning # Receive mode warning
receive_warning = QtWidgets.QLabel(strings._('gui_receive_mode_warning', True)) receive_warning = QtWidgets.QLabel(strings._('gui_receive_mode_warning'))
receive_warning.setMinimumHeight(80) receive_warning.setMinimumHeight(80)
receive_warning.setWordWrap(True) receive_warning.setWordWrap(True)
@ -90,7 +90,7 @@ class ReceiveMode(Mode):
""" """
Return the string to put on the stop server button, if there's a shutdown timeout Return the string to put on the stop server button, if there's a shutdown timeout
""" """
return strings._('gui_receive_stop_server_shutdown_timeout', True) return strings._('gui_receive_stop_server_shutdown_timeout')
def timeout_finished_should_stop_server(self): def timeout_finished_should_stop_server(self):
""" """
@ -128,7 +128,7 @@ class ReceiveMode(Mode):
""" """
Handle REQUEST_LOAD event. Handle REQUEST_LOAD event.
""" """
self.system_tray.showMessage(strings._('systray_page_loaded_title', True), strings._('systray_upload_page_loaded_message', True)) self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_upload_page_loaded_message'))
def handle_request_started(self, event): def handle_request_started(self, event):
""" """
@ -140,7 +140,7 @@ class ReceiveMode(Mode):
self.history.in_progress_count += 1 self.history.in_progress_count += 1
self.history.update_in_progress() self.history.update_in_progress()
self.system_tray.showMessage(strings._('systray_upload_started_title', True), strings._('systray_upload_started_message', True)) self.system_tray.showMessage(strings._('systray_upload_started_title'), strings._('systray_upload_started_message'))
def handle_request_progress(self, event): def handle_request_progress(self, event):
""" """
@ -156,7 +156,7 @@ class ReceiveMode(Mode):
Handle REQUEST_CLOSE_SERVER event. Handle REQUEST_CLOSE_SERVER event.
""" """
self.stop_server() self.stop_server()
self.system_tray.showMessage(strings._('systray_close_server_title', True), strings._('systray_close_server_message', True)) self.system_tray.showMessage(strings._('systray_close_server_title'), strings._('systray_close_server_message'))
def handle_request_upload_file_renamed(self, event): def handle_request_upload_file_renamed(self, event):
""" """

View File

@ -125,7 +125,7 @@ class ShareMode(Mode):
""" """
Return the string to put on the stop server button, if there's a shutdown timeout Return the string to put on the stop server button, if there's a shutdown timeout
""" """
return strings._('gui_share_stop_server_shutdown_timeout', True) return strings._('gui_share_stop_server_shutdown_timeout')
def timeout_finished_should_stop_server(self): def timeout_finished_should_stop_server(self):
""" """
@ -134,11 +134,11 @@ class ShareMode(Mode):
# If there were no attempts to download the share, or all downloads are done, we can stop # If there were no attempts to download the share, or all downloads are done, we can stop
if self.web.share_mode.download_count == 0 or self.web.done: if self.web.share_mode.download_count == 0 or self.web.done:
self.server_status.stop_server() self.server_status.stop_server()
self.server_status_label.setText(strings._('close_on_timeout', True)) self.server_status_label.setText(strings._('close_on_timeout'))
return True return True
# A download is probably still running - hold off on stopping the share # A download is probably still running - hold off on stopping the share
else: else:
self.server_status_label.setText(strings._('timeout_download_still_running', True)) self.server_status_label.setText(strings._('timeout_download_still_running'))
return False return False
def start_server_custom(self): def start_server_custom(self):
@ -185,7 +185,7 @@ class ShareMode(Mode):
# Warn about sending large files over Tor # Warn about sending large files over Tor
if self.web.share_mode.download_filesize >= 157286400: # 150mb if self.web.share_mode.download_filesize >= 157286400: # 150mb
self.filesize_warning.setText(strings._("large_filesize", True)) self.filesize_warning.setText(strings._("large_filesize"))
self.filesize_warning.show() self.filesize_warning.show()
def start_server_error_custom(self): def start_server_error_custom(self):
@ -229,7 +229,7 @@ class ShareMode(Mode):
""" """
Handle REQUEST_LOAD event. Handle REQUEST_LOAD event.
""" """
self.system_tray.showMessage(strings._('systray_page_loaded_title', True), strings._('systray_download_page_loaded_message', True)) self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_download_page_loaded_message'))
def handle_request_started(self, event): def handle_request_started(self, event):
""" """
@ -246,7 +246,7 @@ class ShareMode(Mode):
self.history.in_progress_count += 1 self.history.in_progress_count += 1
self.history.update_in_progress() self.history.update_in_progress()
self.system_tray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) self.system_tray.showMessage(strings._('systray_download_started_title'), strings._('systray_download_started_message'))
def handle_request_progress(self, event): def handle_request_progress(self, event):
""" """
@ -256,7 +256,7 @@ class ShareMode(Mode):
# Is the download complete? # Is the download complete?
if event["data"]["bytes"] == self.web.share_mode.filesize: if event["data"]["bytes"] == self.web.share_mode.filesize:
self.system_tray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True)) self.system_tray.showMessage(strings._('systray_download_completed_title'), strings._('systray_download_completed_message'))
# Update completed and in progress labels # Update completed and in progress labels
self.history.completed_count += 1 self.history.completed_count += 1
@ -268,7 +268,7 @@ class ShareMode(Mode):
if self.common.settings.get('close_after_first_download'): if self.common.settings.get('close_after_first_download'):
self.server_status.stop_server() self.server_status.stop_server()
self.status_bar.clearMessage() self.status_bar.clearMessage()
self.server_status_label.setText(strings._('closing_automatically', True)) self.server_status_label.setText(strings._('closing_automatically'))
else: else:
if self.server_status.status == self.server_status.STATUS_STOPPED: if self.server_status.status == self.server_status.STATUS_STOPPED:
self.history.cancel(event["data"]["id"]) self.history.cancel(event["data"]["id"])
@ -312,9 +312,9 @@ class ShareMode(Mode):
total_size_readable = self.common.human_readable_filesize(total_size_bytes) total_size_readable = self.common.human_readable_filesize(total_size_bytes)
if file_count > 1: if file_count > 1:
self.info_label.setText(strings._('gui_file_info', True).format(file_count, total_size_readable)) self.info_label.setText(strings._('gui_file_info').format(file_count, total_size_readable))
else: else:
self.info_label.setText(strings._('gui_file_info_single', True).format(file_count, total_size_readable)) self.info_label.setText(strings._('gui_file_info_single').format(file_count, total_size_readable))
else: else:
self.primary_action.hide() self.primary_action.hide()

View File

@ -41,7 +41,7 @@ class DropHereLabel(QtWidgets.QLabel):
if image: if image:
self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png')))) self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png'))))
else: else:
self.setText(strings._('gui_drag_and_drop', True)) self.setText(strings._('gui_drag_and_drop'))
self.setStyleSheet(self.common.css['share_file_selection_drop_here_label']) self.setStyleSheet(self.common.css['share_file_selection_drop_here_label'])
self.hide() self.hide()
@ -65,7 +65,7 @@ class DropCountLabel(QtWidgets.QLabel):
self.setAcceptDrops(True) self.setAcceptDrops(True)
self.setAlignment(QtCore.Qt.AlignCenter) self.setAlignment(QtCore.Qt.AlignCenter)
self.setText(strings._('gui_drag_and_drop', True)) self.setText(strings._('gui_drag_and_drop'))
self.setStyleSheet(self.common.css['share_file_selection_drop_count_label']) self.setStyleSheet(self.common.css['share_file_selection_drop_count_label'])
self.hide() self.hide()
@ -216,7 +216,7 @@ class FileList(QtWidgets.QListWidget):
if filename not in filenames: if filename not in filenames:
if not os.access(filename, os.R_OK): if not os.access(filename, os.R_OK):
Alert(self.common, strings._("not_a_readable_file", True).format(filename)) Alert(self.common, strings._("not_a_readable_file").format(filename))
return return
fileinfo = QtCore.QFileInfo(filename) fileinfo = QtCore.QFileInfo(filename)
@ -302,9 +302,9 @@ class FileSelection(QtWidgets.QVBoxLayout):
self.file_list.files_updated.connect(self.update) self.file_list.files_updated.connect(self.update)
# Buttons # Buttons
self.add_button = QtWidgets.QPushButton(strings._('gui_add', True)) self.add_button = QtWidgets.QPushButton(strings._('gui_add'))
self.add_button.clicked.connect(self.add) self.add_button.clicked.connect(self.add)
self.delete_button = QtWidgets.QPushButton(strings._('gui_delete', True)) self.delete_button = QtWidgets.QPushButton(strings._('gui_delete'))
self.delete_button.clicked.connect(self.delete) self.delete_button.clicked.connect(self.delete)
button_layout = QtWidgets.QHBoxLayout() button_layout = QtWidgets.QHBoxLayout()
button_layout.addStretch() button_layout.addStretch()
@ -341,7 +341,7 @@ class FileSelection(QtWidgets.QVBoxLayout):
""" """
Add button clicked. Add button clicked.
""" """
file_dialog = AddFileDialog(self.common, caption=strings._('gui_choose_items', True)) file_dialog = AddFileDialog(self.common, caption=strings._('gui_choose_items'))
if file_dialog.exec_() == QtWidgets.QDialog.Accepted: if file_dialog.exec_() == QtWidgets.QDialog.Accepted:
for filename in file_dialog.selectedFiles(): for filename in file_dialog.selectedFiles():
self.file_list.add_file(filename) self.file_list.add_file(filename)

View File

@ -58,17 +58,18 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.setWindowTitle('OnionShare') self.setWindowTitle('OnionShare')
self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
# Load settings # Load settings, if a custom config was passed in
self.config = config self.config = config
if self.config:
self.common.load_settings(self.config) self.common.load_settings(self.config)
# System tray # System tray
menu = QtWidgets.QMenu() menu = QtWidgets.QMenu()
self.settings_action = menu.addAction(strings._('gui_settings_window_title', True)) self.settings_action = menu.addAction(strings._('gui_settings_window_title'))
self.settings_action.triggered.connect(self.open_settings) self.settings_action.triggered.connect(self.open_settings)
help_action = menu.addAction(strings._('gui_settings_button_help', True)) help_action = menu.addAction(strings._('gui_settings_button_help'))
help_action.triggered.connect(SettingsDialog.open_help) help_action.triggered.connect(SettingsDialog.help_clicked)
exit_action = menu.addAction(strings._('systray_menu_exit', True)) exit_action = menu.addAction(strings._('systray_menu_exit'))
exit_action.triggered.connect(self.close) exit_action.triggered.connect(self.close)
self.system_tray = QtWidgets.QSystemTrayIcon(self) self.system_tray = QtWidgets.QSystemTrayIcon(self)
@ -81,10 +82,10 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.system_tray.show() self.system_tray.show()
# Mode switcher, to switch between share files and receive files # Mode switcher, to switch between share files and receive files
self.share_mode_button = QtWidgets.QPushButton(strings._('gui_mode_share_button', True)); self.share_mode_button = QtWidgets.QPushButton(strings._('gui_mode_share_button'));
self.share_mode_button.setFixedHeight(50) self.share_mode_button.setFixedHeight(50)
self.share_mode_button.clicked.connect(self.share_mode_clicked) self.share_mode_button.clicked.connect(self.share_mode_clicked)
self.receive_mode_button = QtWidgets.QPushButton(strings._('gui_mode_receive_button', True)); self.receive_mode_button = QtWidgets.QPushButton(strings._('gui_mode_receive_button'));
self.receive_mode_button.setFixedHeight(50) self.receive_mode_button.setFixedHeight(50)
self.receive_mode_button.clicked.connect(self.receive_mode_clicked) self.receive_mode_button.clicked.connect(self.receive_mode_clicked)
self.settings_button = QtWidgets.QPushButton() self.settings_button = QtWidgets.QPushButton()
@ -224,24 +225,24 @@ class OnionShareGui(QtWidgets.QMainWindow):
# Share mode # Share mode
if self.share_mode.server_status.status == ServerStatus.STATUS_STOPPED: if self.share_mode.server_status.status == ServerStatus.STATUS_STOPPED:
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped))
self.server_status_label.setText(strings._('gui_status_indicator_share_stopped', True)) self.server_status_label.setText(strings._('gui_status_indicator_share_stopped'))
elif self.share_mode.server_status.status == ServerStatus.STATUS_WORKING: elif self.share_mode.server_status.status == ServerStatus.STATUS_WORKING:
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working))
self.server_status_label.setText(strings._('gui_status_indicator_share_working', True)) self.server_status_label.setText(strings._('gui_status_indicator_share_working'))
elif self.share_mode.server_status.status == ServerStatus.STATUS_STARTED: 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_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started))
self.server_status_label.setText(strings._('gui_status_indicator_share_started', True)) self.server_status_label.setText(strings._('gui_status_indicator_share_started'))
else: else:
# Receive mode # Receive mode
if self.receive_mode.server_status.status == ServerStatus.STATUS_STOPPED: if self.receive_mode.server_status.status == ServerStatus.STATUS_STOPPED:
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped))
self.server_status_label.setText(strings._('gui_status_indicator_receive_stopped', True)) self.server_status_label.setText(strings._('gui_status_indicator_receive_stopped'))
elif self.receive_mode.server_status.status == ServerStatus.STATUS_WORKING: elif self.receive_mode.server_status.status == ServerStatus.STATUS_WORKING:
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working))
self.server_status_label.setText(strings._('gui_status_indicator_receive_working', True)) self.server_status_label.setText(strings._('gui_status_indicator_receive_working'))
elif self.receive_mode.server_status.status == ServerStatus.STATUS_STARTED: 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_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started))
self.server_status_label.setText(strings._('gui_status_indicator_receive_started', True)) self.server_status_label.setText(strings._('gui_status_indicator_receive_started'))
def stop_server_finished(self): def stop_server_finished(self):
# When the server stopped, cleanup the ephemeral onion service # When the server stopped, cleanup the ephemeral onion service
@ -255,9 +256,9 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.common.log('OnionShareGui', '_tor_connection_canceled') self.common.log('OnionShareGui', '_tor_connection_canceled')
def ask(): def ask():
a = Alert(self.common, strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) a = Alert(self.common, strings._('gui_tor_connection_ask'), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False)
settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings', True)) settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings'))
quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit', True)) quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit'))
a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole) a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole)
a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole) a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole)
a.setDefaultButton(settings_button) a.setDefaultButton(settings_button)
@ -328,7 +329,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
if self.common.platform == 'Windows' or self.common.platform == 'Darwin': if self.common.platform == 'Windows' or self.common.platform == 'Darwin':
if self.common.settings.get('use_autoupdate'): if self.common.settings.get('use_autoupdate'):
def update_available(update_url, installed_version, latest_version): def update_available(update_url, installed_version, latest_version):
Alert(self.common, strings._("update_available", True).format(update_url, installed_version, latest_version)) Alert(self.common, strings._("update_available").format(update_url, installed_version, latest_version))
self.update_thread = UpdateThread(self.common, self.onion, self.config) self.update_thread = UpdateThread(self.common, self.onion, self.config)
self.update_thread.update_available.connect(update_available) self.update_thread.update_available.connect(update_available)
@ -345,8 +346,8 @@ class OnionShareGui(QtWidgets.QMainWindow):
# Have we lost connection to Tor somehow? # Have we lost connection to Tor somehow?
if not self.onion.is_authenticated(): if not self.onion.is_authenticated():
self.timer.stop() self.timer.stop()
self.status_bar.showMessage(strings._('gui_tor_connection_lost', True)) self.status_bar.showMessage(strings._('gui_tor_connection_lost'))
self.system_tray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True)) self.system_tray.showMessage(strings._('gui_tor_connection_lost'), strings._('gui_tor_connection_error_settings'))
self.share_mode.handle_tor_broke() self.share_mode.handle_tor_broke()
self.receive_mode.handle_tor_broke() self.receive_mode.handle_tor_broke()
@ -400,7 +401,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
if event["type"] == Web.REQUEST_OTHER: if event["type"] == Web.REQUEST_OTHER:
if event["path"] != '/favicon.ico' and event["path"] != "/{}/shutdown".format(mode.web.shutdown_slug): if event["path"] != '/favicon.ico' and event["path"] != "/{}/shutdown".format(mode.web.shutdown_slug):
self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.error404_count, strings._('other_page_loaded', True), event["path"])) self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.error404_count, strings._('other_page_loaded'), event["path"]))
mode.timer_callback() mode.timer_callback()
@ -409,14 +410,14 @@ class OnionShareGui(QtWidgets.QMainWindow):
When the URL gets copied to the clipboard, display this in the status bar. When the URL gets copied to the clipboard, display this in the status bar.
""" """
self.common.log('OnionShareGui', 'copy_url') self.common.log('OnionShareGui', 'copy_url')
self.system_tray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True)) self.system_tray.showMessage(strings._('gui_copied_url_title'), strings._('gui_copied_url'))
def copy_hidservauth(self): def copy_hidservauth(self):
""" """
When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar. When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar.
""" """
self.common.log('OnionShareGui', 'copy_hidservauth') self.common.log('OnionShareGui', 'copy_hidservauth')
self.system_tray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True)) self.system_tray.showMessage(strings._('gui_copied_hidservauth_title'), strings._('gui_copied_hidservauth'))
def clear_message(self): def clear_message(self):
""" """
@ -454,14 +455,14 @@ class OnionShareGui(QtWidgets.QMainWindow):
if server_status.status != server_status.STATUS_STOPPED: if server_status.status != server_status.STATUS_STOPPED:
self.common.log('OnionShareGui', 'closeEvent, opening warning dialog') self.common.log('OnionShareGui', 'closeEvent, opening warning dialog')
dialog = QtWidgets.QMessageBox() dialog = QtWidgets.QMessageBox()
dialog.setWindowTitle(strings._('gui_quit_title', True)) dialog.setWindowTitle(strings._('gui_quit_title'))
if self.mode == OnionShareGui.MODE_SHARE: if self.mode == OnionShareGui.MODE_SHARE:
dialog.setText(strings._('gui_share_quit_warning', True)) dialog.setText(strings._('gui_share_quit_warning'))
else: else:
dialog.setText(strings._('gui_receive_quit_warning', True)) dialog.setText(strings._('gui_receive_quit_warning'))
dialog.setIcon(QtWidgets.QMessageBox.Critical) dialog.setIcon(QtWidgets.QMessageBox.Critical)
quit_button = dialog.addButton(strings._('gui_quit_warning_quit', True), QtWidgets.QMessageBox.YesRole) quit_button = dialog.addButton(strings._('gui_quit_warning_quit'), QtWidgets.QMessageBox.YesRole)
dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit', True), QtWidgets.QMessageBox.NoRole) dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit'), QtWidgets.QMessageBox.NoRole)
dialog.setDefaultButton(dont_quit_button) dialog.setDefaultButton(dont_quit_button)
reply = dialog.exec_() reply = dialog.exec_()

View File

@ -0,0 +1,320 @@
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import os
import subprocess
import textwrap
from datetime import datetime
from PyQt5 import QtCore, QtWidgets, QtGui
from onionshare import strings
from ..widgets import Alert
class File(QtWidgets.QWidget):
def __init__(self, common, filename):
super(File, self).__init__()
self.common = common
self.common.log('File', '__init__', 'filename: {}'.format(filename))
self.filename = filename
self.started = datetime.now()
# Filename label
self.filename_label = QtWidgets.QLabel(self.filename)
self.filename_label_width = self.filename_label.width()
# File size label
self.filesize_label = QtWidgets.QLabel()
self.filesize_label.setStyleSheet(self.common.css['receive_file_size'])
self.filesize_label.hide()
# Folder button
folder_pixmap = QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/open_folder.png')))
folder_icon = QtGui.QIcon(folder_pixmap)
self.folder_button = QtWidgets.QPushButton()
self.folder_button.clicked.connect(self.open_folder)
self.folder_button.setIcon(folder_icon)
self.folder_button.setIconSize(folder_pixmap.rect().size())
self.folder_button.setFlat(True)
self.folder_button.hide()
# Layouts
layout = QtWidgets.QHBoxLayout()
layout.addWidget(self.filename_label)
layout.addWidget(self.filesize_label)
layout.addStretch()
layout.addWidget(self.folder_button)
self.setLayout(layout)
def update(self, uploaded_bytes, complete):
self.filesize_label.setText(self.common.human_readable_filesize(uploaded_bytes))
self.filesize_label.show()
if complete:
self.folder_button.show()
def rename(self, new_filename):
self.filename = new_filename
self.filename_label.setText(self.filename)
def open_folder(self):
"""
Open the downloads folder, with the file selected, in a cross-platform manner
"""
self.common.log('File', 'open_folder')
abs_filename = os.path.join(self.common.settings.get('downloads_dir'), self.filename)
# Linux
if self.common.platform == 'Linux' or self.common.platform == 'BSD':
try:
# If nautilus is available, open it
subprocess.Popen(['nautilus', abs_filename])
except:
Alert(self.common, strings._('gui_open_folder_error_nautilus').format(abs_filename))
# macOS
elif self.common.platform == 'Darwin':
# TODO: Implement opening folder with file selected in macOS
# This seems helpful: https://stackoverflow.com/questions/3520493/python-show-in-finder
self.common.log('File', 'open_folder', 'not implemented for Darwin yet')
# Windows
elif self.common.platform == 'Windows':
# TODO: Implement opening folder with file selected in Windows
# This seems helpful: https://stackoverflow.com/questions/6631299/python-opening-a-folder-in-explorer-nautilus-mac-thingie
self.common.log('File', 'open_folder', 'not implemented for Windows yet')
class Upload(QtWidgets.QWidget):
def __init__(self, common, upload_id, content_length):
super(Upload, self).__init__()
self.common = common
self.upload_id = upload_id
self.content_length = content_length
self.started = datetime.now()
# Label
self.label = QtWidgets.QLabel(strings._('gui_upload_in_progress').format(self.started.strftime("%b %d, %I:%M%p")))
# Progress bar
self.progress_bar = QtWidgets.QProgressBar()
self.progress_bar.setTextVisible(True)
self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter)
self.progress_bar.setMinimum(0)
self.progress_bar.setValue(0)
self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar'])
# This layout contains file widgets
self.files_layout = QtWidgets.QVBoxLayout()
self.files_layout.setContentsMargins(0, 0, 0, 0)
files_widget = QtWidgets.QWidget()
files_widget.setStyleSheet(self.common.css['receive_file'])
files_widget.setLayout(self.files_layout)
# Layout
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.label)
layout.addWidget(self.progress_bar)
layout.addWidget(files_widget)
layout.addStretch()
self.setLayout(layout)
# We're also making a dictionary of file widgets, to make them easier to access
self.files = {}
def update(self, progress):
"""
Using the progress from Web, update the progress bar and file size labels
for each file
"""
total_uploaded_bytes = 0
for filename in progress:
total_uploaded_bytes += progress[filename]['uploaded_bytes']
# Update the progress bar
self.progress_bar.setMaximum(self.content_length)
self.progress_bar.setValue(total_uploaded_bytes)
elapsed = datetime.now() - self.started
if elapsed.seconds < 10:
pb_fmt = strings._('gui_download_upload_progress_starting').format(
self.common.human_readable_filesize(total_uploaded_bytes))
else:
estimated_time_remaining = self.common.estimated_time_remaining(
total_uploaded_bytes,
self.content_length,
self.started.timestamp())
pb_fmt = strings._('gui_download_upload_progress_eta').format(
self.common.human_readable_filesize(total_uploaded_bytes),
estimated_time_remaining)
# Using list(progress) to avoid "RuntimeError: dictionary changed size during iteration"
for filename in list(progress):
# Add a new file if needed
if filename not in self.files:
self.files[filename] = File(self.common, filename)
self.files_layout.addWidget(self.files[filename])
# Update the file
self.files[filename].update(progress[filename]['uploaded_bytes'], progress[filename]['complete'])
def rename(self, old_filename, new_filename):
self.files[old_filename].rename(new_filename)
self.files[new_filename] = self.files.pop(old_filename)
def finished(self):
# Hide the progress bar
self.progress_bar.hide()
# Change the label
self.ended = self.started = datetime.now()
if self.started.year == self.ended.year and self.started.month == self.ended.month and self.started.day == self.ended.day:
if self.started.hour == self.ended.hour and self.started.minute == self.ended.minute:
text = strings._('gui_upload_finished').format(
self.started.strftime("%b %d, %I:%M%p")
)
else:
text = strings._('gui_upload_finished_range').format(
self.started.strftime("%b %d, %I:%M%p"),
self.ended.strftime("%I:%M%p")
)
else:
text = strings._('gui_upload_finished_range').format(
self.started.strftime("%b %d, %I:%M%p"),
self.ended.strftime("%b %d, %I:%M%p")
)
self.label.setText(text)
class Uploads(QtWidgets.QScrollArea):
"""
The uploads chunk of the GUI. This lists all of the active upload
progress bars, as well as information about each upload.
"""
def __init__(self, common):
super(Uploads, self).__init__()
self.common = common
self.common.log('Uploads', '__init__')
self.resizeEvent = None
self.uploads = {}
self.setWindowTitle(strings._('gui_uploads'))
self.setWidgetResizable(True)
self.setMinimumHeight(150)
self.setMinimumWidth(350)
self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
self.setWindowFlags(QtCore.Qt.Sheet | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.CustomizeWindowHint)
self.vbar = self.verticalScrollBar()
self.vbar.rangeChanged.connect(self.resizeScroll)
uploads_label = QtWidgets.QLabel(strings._('gui_uploads'))
uploads_label.setStyleSheet(self.common.css['downloads_uploads_label'])
self.no_uploads_label = QtWidgets.QLabel(strings._('gui_no_uploads'))
self.clear_history_button = QtWidgets.QPushButton(strings._('gui_clear_history'))
self.clear_history_button.clicked.connect(self.reset)
self.clear_history_button.hide()
self.uploads_layout = QtWidgets.QVBoxLayout()
widget = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(uploads_label)
layout.addWidget(self.no_uploads_label)
layout.addWidget(self.clear_history_button)
layout.addLayout(self.uploads_layout)
layout.addStretch()
widget.setLayout(layout)
self.setWidget(widget)
def resizeScroll(self, minimum, maximum):
"""
Scroll to the bottom of the window when the range changes.
"""
self.vbar.setValue(maximum)
def add(self, upload_id, content_length):
"""
Add a new upload.
"""
self.common.log('Uploads', 'add', 'upload_id: {}, content_length: {}'.format(upload_id, content_length))
# Hide the no_uploads_label
self.no_uploads_label.hide()
# Show the clear_history_button
self.clear_history_button.show()
# Add it to the list
upload = Upload(self.common, upload_id, content_length)
self.uploads[upload_id] = upload
self.uploads_layout.addWidget(upload)
def update(self, upload_id, progress):
"""
Update the progress of an upload.
"""
self.uploads[upload_id].update(progress)
def rename(self, upload_id, old_filename, new_filename):
"""
Rename a file, which happens if the filename already exists in downloads_dir.
"""
self.uploads[upload_id].rename(old_filename, new_filename)
def finished(self, upload_id):
"""
An upload has finished.
"""
self.uploads[upload_id].finished()
def cancel(self, upload_id):
"""
Update an upload progress bar to show that it has been canceled.
"""
self.common.log('Uploads', 'cancel', 'upload_id: {}'.format(upload_id))
self.uploads[upload_id].cancel()
def reset(self):
"""
Reset the uploads back to zero
"""
self.common.log('Uploads', 'reset')
for upload in self.uploads.values():
upload.close()
self.uploads_layout.removeWidget(upload)
self.uploads = {}
self.no_uploads_label.show()
self.clear_history_button.hide()
self.resize(self.sizeHint())
def resizeEvent(self, event):
width = self.frameGeometry().width()
try:
for upload in self.uploads.values():
for item in upload.files.values():
item.filename_label.setText(textwrap.fill(item.filename, 30))
item.adjustSize()
except:
pass

View File

@ -61,7 +61,7 @@ class ServerStatus(QtWidgets.QWidget):
self.resizeEvent(None) self.resizeEvent(None)
# Shutdown timeout layout # Shutdown timeout layout
self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout'))
self.shutdown_timeout = QtWidgets.QDateTimeEdit() self.shutdown_timeout = QtWidgets.QDateTimeEdit()
self.shutdown_timeout.setDisplayFormat("hh:mm A MMM d, yy") self.shutdown_timeout.setDisplayFormat("hh:mm A MMM d, yy")
if self.local_only: if self.local_only:
@ -100,12 +100,12 @@ class ServerStatus(QtWidgets.QWidget):
self.url.setMinimumSize(self.url.sizeHint()) self.url.setMinimumSize(self.url.sizeHint())
self.url.setStyleSheet(self.common.css['server_status_url']) self.url.setStyleSheet(self.common.css['server_status_url'])
self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url', True)) self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url'))
self.copy_url_button.setFlat(True) self.copy_url_button.setFlat(True)
self.copy_url_button.setStyleSheet(self.common.css['server_status_url_buttons']) self.copy_url_button.setStyleSheet(self.common.css['server_status_url_buttons'])
self.copy_url_button.setMinimumHeight(65) self.copy_url_button.setMinimumHeight(65)
self.copy_url_button.clicked.connect(self.copy_url) self.copy_url_button.clicked.connect(self.copy_url)
self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True)) self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth'))
self.copy_hidservauth_button.setFlat(True) self.copy_hidservauth_button.setFlat(True)
self.copy_hidservauth_button.setStyleSheet(self.common.css['server_status_url_buttons']) self.copy_hidservauth_button.setStyleSheet(self.common.css['server_status_url_buttons'])
self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth) self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth)
@ -174,21 +174,21 @@ class ServerStatus(QtWidgets.QWidget):
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: if self.mode == ServerStatus.MODE_SHARE:
self.url_description.setText(strings._('gui_share_url_description', True).format(info_image)) self.url_description.setText(strings._('gui_share_url_description').format(info_image))
else: else:
self.url_description.setText(strings._('gui_receive_url_description', True).format(info_image)) self.url_description.setText(strings._('gui_receive_url_description').format(info_image))
# Show a Tool Tip explaining the lifecycle of this URL # Show a Tool Tip explaining the lifecycle of this URL
if self.common.settings.get('save_private_key'): if self.common.settings.get('save_private_key'):
if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get('close_after_first_download'): 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', True)) self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent'))
else: else:
self.url_description.setToolTip(strings._('gui_url_label_persistent', True)) self.url_description.setToolTip(strings._('gui_url_label_persistent'))
else: else:
if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get('close_after_first_download'): if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get('close_after_first_download'):
self.url_description.setToolTip(strings._('gui_url_label_onetime', True)) self.url_description.setToolTip(strings._('gui_url_label_onetime'))
else: else:
self.url_description.setToolTip(strings._('gui_url_label_stay_open', True)) self.url_description.setToolTip(strings._('gui_url_label_stay_open'))
self.url.setText(self.get_url()) self.url.setText(self.get_url())
self.url.show() self.url.show()
@ -223,9 +223,9 @@ class ServerStatus(QtWidgets.QWidget):
self.server_button.setStyleSheet(self.common.css['server_status_button_stopped']) self.server_button.setStyleSheet(self.common.css['server_status_button_stopped'])
self.server_button.setEnabled(True) self.server_button.setEnabled(True)
if self.mode == ServerStatus.MODE_SHARE: if self.mode == ServerStatus.MODE_SHARE:
self.server_button.setText(strings._('gui_share_start_server', True)) self.server_button.setText(strings._('gui_share_start_server'))
else: else:
self.server_button.setText(strings._('gui_receive_start_server', True)) self.server_button.setText(strings._('gui_receive_start_server'))
self.server_button.setToolTip('') self.server_button.setToolTip('')
if self.common.settings.get('shutdown_timeout'): if self.common.settings.get('shutdown_timeout'):
self.shutdown_timeout_container.show() self.shutdown_timeout_container.show()
@ -233,15 +233,15 @@ class ServerStatus(QtWidgets.QWidget):
self.server_button.setStyleSheet(self.common.css['server_status_button_started']) self.server_button.setStyleSheet(self.common.css['server_status_button_started'])
self.server_button.setEnabled(True) self.server_button.setEnabled(True)
if self.mode == ServerStatus.MODE_SHARE: if self.mode == ServerStatus.MODE_SHARE:
self.server_button.setText(strings._('gui_share_stop_server', True)) self.server_button.setText(strings._('gui_share_stop_server'))
else: else:
self.server_button.setText(strings._('gui_receive_stop_server', True)) self.server_button.setText(strings._('gui_receive_stop_server'))
if self.common.settings.get('shutdown_timeout'): if self.common.settings.get('shutdown_timeout'):
self.shutdown_timeout_container.hide() self.shutdown_timeout_container.hide()
if self.mode == ServerStatus.MODE_SHARE: if self.mode == ServerStatus.MODE_SHARE:
self.server_button.setToolTip(strings._('gui_share_stop_server_shutdown_timeout_tooltip', True).format(self.timeout)) self.server_button.setToolTip(strings._('gui_share_stop_server_shutdown_timeout_tooltip').format(self.timeout))
else: else:
self.server_button.setToolTip(strings._('gui_receive_stop_server_shutdown_timeout_tooltip', True).format(self.timeout)) self.server_button.setToolTip(strings._('gui_receive_stop_server_shutdown_timeout_tooltip').format(self.timeout))
elif self.status == self.STATUS_WORKING: elif self.status == self.STATUS_WORKING:
self.server_button.setStyleSheet(self.common.css['server_status_button_working']) self.server_button.setStyleSheet(self.common.css['server_status_button_working'])

View File

@ -47,7 +47,7 @@ class SettingsDialog(QtWidgets.QDialog):
self.local_only = local_only self.local_only = local_only
self.setModal(True) self.setModal(True)
self.setWindowTitle(strings._('gui_settings_window_title', True)) self.setWindowTitle(strings._('gui_settings_window_title'))
self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
self.system = platform.system() self.system = platform.system()
@ -57,8 +57,8 @@ class SettingsDialog(QtWidgets.QDialog):
# Use a slug or not ('public mode') # Use a slug or not ('public mode')
self.public_mode_checkbox = QtWidgets.QCheckBox() self.public_mode_checkbox = QtWidgets.QCheckBox()
self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked)
self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox", True)) self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox"))
public_mode_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Public-Mode")) public_mode_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Public-Mode"))
public_mode_label.setStyleSheet(self.common.css['settings_whats_this']) public_mode_label.setStyleSheet(self.common.css['settings_whats_this'])
public_mode_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) public_mode_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
public_mode_label.setOpenExternalLinks(True) public_mode_label.setOpenExternalLinks(True)
@ -74,8 +74,8 @@ class SettingsDialog(QtWidgets.QDialog):
# Whether or not to use a shutdown ('auto-stop') timer # Whether or not to use a shutdown ('auto-stop') timer
self.shutdown_timeout_checkbox = QtWidgets.QCheckBox() self.shutdown_timeout_checkbox = QtWidgets.QCheckBox()
self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked)
self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox"))
shutdown_timeout_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Stop-Timer")) shutdown_timeout_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Stop-Timer"))
shutdown_timeout_label.setStyleSheet(self.common.css['settings_whats_this']) shutdown_timeout_label.setStyleSheet(self.common.css['settings_whats_this'])
shutdown_timeout_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) shutdown_timeout_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
shutdown_timeout_label.setOpenExternalLinks(True) shutdown_timeout_label.setOpenExternalLinks(True)
@ -91,9 +91,9 @@ class SettingsDialog(QtWidgets.QDialog):
# Whether or not to use legacy v2 onions # Whether or not to use legacy v2 onions
self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox()
self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Unchecked) self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Unchecked)
self.use_legacy_v2_onions_checkbox.setText(strings._("gui_use_legacy_v2_onions_checkbox", True)) self.use_legacy_v2_onions_checkbox.setText(strings._("gui_use_legacy_v2_onions_checkbox"))
self.use_legacy_v2_onions_checkbox.clicked.connect(self.use_legacy_v2_onions_checkbox_clicked) self.use_legacy_v2_onions_checkbox.clicked.connect(self.use_legacy_v2_onions_checkbox_clicked)
use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Legacy-Addresses")) use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Legacy-Addresses"))
use_legacy_v2_onions_label.setStyleSheet(self.common.css['settings_whats_this']) use_legacy_v2_onions_label.setStyleSheet(self.common.css['settings_whats_this'])
use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
use_legacy_v2_onions_label.setOpenExternalLinks(True) use_legacy_v2_onions_label.setOpenExternalLinks(True)
@ -108,9 +108,9 @@ class SettingsDialog(QtWidgets.QDialog):
# Whether or not to save the Onion private key for reuse (persistent URL mode) # Whether or not to save the Onion private key for reuse (persistent URL mode)
self.save_private_key_checkbox = QtWidgets.QCheckBox() self.save_private_key_checkbox = QtWidgets.QCheckBox()
self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked)
self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox", True)) self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox"))
self.save_private_key_checkbox.clicked.connect(self.save_private_key_checkbox_clicked) self.save_private_key_checkbox.clicked.connect(self.save_private_key_checkbox_clicked)
save_private_key_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Using-a-Persistent-URL")) save_private_key_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-a-Persistent-URL"))
save_private_key_label.setStyleSheet(self.common.css['settings_whats_this']) save_private_key_label.setStyleSheet(self.common.css['settings_whats_this'])
save_private_key_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) save_private_key_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
save_private_key_label.setOpenExternalLinks(True) save_private_key_label.setOpenExternalLinks(True)
@ -125,9 +125,9 @@ class SettingsDialog(QtWidgets.QDialog):
# Stealth # Stealth
self.stealth_checkbox = QtWidgets.QCheckBox() self.stealth_checkbox = QtWidgets.QCheckBox()
self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked)
self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) self.stealth_checkbox.setText(strings._("gui_settings_stealth_option"))
self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect) self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect)
use_stealth_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services")) use_stealth_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services"))
use_stealth_label.setStyleSheet(self.common.css['settings_whats_this']) use_stealth_label.setStyleSheet(self.common.css['settings_whats_this'])
use_stealth_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_stealth_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
use_stealth_label.setOpenExternalLinks(True) use_stealth_label.setOpenExternalLinks(True)
@ -140,12 +140,12 @@ class SettingsDialog(QtWidgets.QDialog):
self.use_stealth_widget = QtWidgets.QWidget() self.use_stealth_widget = QtWidgets.QWidget()
self.use_stealth_widget.setLayout(use_stealth_layout) self.use_stealth_widget.setLayout(use_stealth_layout)
hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True)) hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string'))
hidservauth_details.setWordWrap(True) hidservauth_details.setWordWrap(True)
hidservauth_details.setMinimumSize(hidservauth_details.sizeHint()) hidservauth_details.setMinimumSize(hidservauth_details.sizeHint())
hidservauth_details.hide() hidservauth_details.hide()
self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True)) self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth'))
self.hidservauth_copy_button.clicked.connect(self.hidservauth_copy_button_clicked) self.hidservauth_copy_button.clicked.connect(self.hidservauth_copy_button_clicked)
self.hidservauth_copy_button.hide() self.hidservauth_copy_button.hide()
@ -159,7 +159,7 @@ class SettingsDialog(QtWidgets.QDialog):
general_group_layout.addWidget(hidservauth_details) general_group_layout.addWidget(hidservauth_details)
general_group_layout.addWidget(self.hidservauth_copy_button) general_group_layout.addWidget(self.hidservauth_copy_button)
general_group = QtWidgets.QGroupBox(strings._("gui_settings_general_label", True)) general_group = QtWidgets.QGroupBox(strings._("gui_settings_general_label"))
general_group.setLayout(general_group_layout) general_group.setLayout(general_group_layout)
# Sharing options # Sharing options
@ -167,19 +167,19 @@ class SettingsDialog(QtWidgets.QDialog):
# Close after first download # Close after first download
self.close_after_first_download_checkbox = QtWidgets.QCheckBox() self.close_after_first_download_checkbox = QtWidgets.QCheckBox()
self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked)
self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option", True)) self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option"))
# Sharing options layout # Sharing options layout
sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout = QtWidgets.QVBoxLayout()
sharing_group_layout.addWidget(self.close_after_first_download_checkbox) sharing_group_layout.addWidget(self.close_after_first_download_checkbox)
sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True)) sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label"))
sharing_group.setLayout(sharing_group_layout) sharing_group.setLayout(sharing_group_layout)
# Downloads dir # Downloads dir
downloads_label = QtWidgets.QLabel(strings._('gui_settings_downloads_label', True)); downloads_label = QtWidgets.QLabel(strings._('gui_settings_downloads_label'));
self.downloads_dir_lineedit = QtWidgets.QLineEdit() self.downloads_dir_lineedit = QtWidgets.QLineEdit()
self.downloads_dir_lineedit.setReadOnly(True) self.downloads_dir_lineedit.setReadOnly(True)
downloads_button = QtWidgets.QPushButton(strings._('gui_settings_downloads_button', True)) downloads_button = QtWidgets.QPushButton(strings._('gui_settings_downloads_button'))
downloads_button.clicked.connect(self.downloads_button_clicked) downloads_button.clicked.connect(self.downloads_button_clicked)
downloads_layout = QtWidgets.QHBoxLayout() downloads_layout = QtWidgets.QHBoxLayout()
downloads_layout.addWidget(downloads_label) downloads_layout.addWidget(downloads_label)
@ -189,13 +189,13 @@ class SettingsDialog(QtWidgets.QDialog):
# Allow the receiver to shutdown the server # Allow the receiver to shutdown the server
self.receive_allow_receiver_shutdown_checkbox = QtWidgets.QCheckBox() self.receive_allow_receiver_shutdown_checkbox = QtWidgets.QCheckBox()
self.receive_allow_receiver_shutdown_checkbox.setCheckState(QtCore.Qt.Checked) self.receive_allow_receiver_shutdown_checkbox.setCheckState(QtCore.Qt.Checked)
self.receive_allow_receiver_shutdown_checkbox.setText(strings._("gui_settings_receive_allow_receiver_shutdown_checkbox", True)) self.receive_allow_receiver_shutdown_checkbox.setText(strings._("gui_settings_receive_allow_receiver_shutdown_checkbox"))
# Receiving options layout # Receiving options layout
receiving_group_layout = QtWidgets.QVBoxLayout() receiving_group_layout = QtWidgets.QVBoxLayout()
receiving_group_layout.addLayout(downloads_layout) receiving_group_layout.addLayout(downloads_layout)
receiving_group_layout.addWidget(self.receive_allow_receiver_shutdown_checkbox) receiving_group_layout.addWidget(self.receive_allow_receiver_shutdown_checkbox)
receiving_group = QtWidgets.QGroupBox(strings._("gui_settings_receiving_label", True)) receiving_group = QtWidgets.QGroupBox(strings._("gui_settings_receiving_label"))
receiving_group.setLayout(receiving_group_layout) receiving_group.setLayout(receiving_group_layout)
# Automatic updates options # Automatic updates options
@ -203,13 +203,13 @@ class SettingsDialog(QtWidgets.QDialog):
# Autoupdate # Autoupdate
self.autoupdate_checkbox = QtWidgets.QCheckBox() self.autoupdate_checkbox = QtWidgets.QCheckBox()
self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked)
self.autoupdate_checkbox.setText(strings._("gui_settings_autoupdate_option", True)) self.autoupdate_checkbox.setText(strings._("gui_settings_autoupdate_option"))
# Last update time # Last update time
self.autoupdate_timestamp = QtWidgets.QLabel() self.autoupdate_timestamp = QtWidgets.QLabel()
# Check for updates button # Check for updates button
self.check_for_updates_button = QtWidgets.QPushButton(strings._('gui_settings_autoupdate_check_button', True)) self.check_for_updates_button = QtWidgets.QPushButton(strings._('gui_settings_autoupdate_check_button'))
self.check_for_updates_button.clicked.connect(self.check_for_updates) self.check_for_updates_button.clicked.connect(self.check_for_updates)
# We can't check for updates if not connected to Tor # We can't check for updates if not connected to Tor
if not self.onion.connected_to_tor: if not self.onion.connected_to_tor:
@ -220,17 +220,32 @@ class SettingsDialog(QtWidgets.QDialog):
autoupdate_group_layout.addWidget(self.autoupdate_checkbox) autoupdate_group_layout.addWidget(self.autoupdate_checkbox)
autoupdate_group_layout.addWidget(self.autoupdate_timestamp) autoupdate_group_layout.addWidget(self.autoupdate_timestamp)
autoupdate_group_layout.addWidget(self.check_for_updates_button) autoupdate_group_layout.addWidget(self.check_for_updates_button)
autoupdate_group = QtWidgets.QGroupBox(strings._("gui_settings_autoupdate_label", True)) autoupdate_group = QtWidgets.QGroupBox(strings._("gui_settings_autoupdate_label"))
autoupdate_group.setLayout(autoupdate_group_layout) autoupdate_group.setLayout(autoupdate_group_layout)
# Autoupdate is only available for Windows and Mac (Linux updates using package manager) # Autoupdate is only available for Windows and Mac (Linux updates using package manager)
if self.system != 'Windows' and self.system != 'Darwin': if self.system != 'Windows' and self.system != 'Darwin':
autoupdate_group.hide() autoupdate_group.hide()
# Language settings
language_label = QtWidgets.QLabel(strings._("gui_settings_language_label"))
self.language_combobox = QtWidgets.QComboBox()
# Populate the dropdown with all of OnionShare's available languages
language_names_to_locales = {v: k for k, v in self.common.settings.available_locales.items()}
language_names = list(language_names_to_locales)
language_names.sort()
for language_name in language_names:
locale = language_names_to_locales[language_name]
self.language_combobox.addItem(language_name, QtCore.QVariant(locale))
language_layout = QtWidgets.QHBoxLayout()
language_layout.addWidget(language_label)
language_layout.addWidget(self.language_combobox)
language_layout.addStretch()
# Connection type: either automatic, control port, or socket file # Connection type: either automatic, control port, or socket file
# Bundled Tor # Bundled Tor
self.connection_type_bundled_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_bundled_option', True)) self.connection_type_bundled_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_bundled_option'))
self.connection_type_bundled_radio.toggled.connect(self.connection_type_bundled_toggled) self.connection_type_bundled_radio.toggled.connect(self.connection_type_bundled_toggled)
# Bundled Tor doesn't work on dev mode in Windows or Mac # Bundled Tor doesn't work on dev mode in Windows or Mac
@ -240,27 +255,27 @@ class SettingsDialog(QtWidgets.QDialog):
# Bridge options for bundled tor # Bridge options for bundled tor
# No bridges option radio # No bridges option radio
self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_no_bridges_radio_option', True)) self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_no_bridges_radio_option'))
self.tor_bridges_no_bridges_radio.toggled.connect(self.tor_bridges_no_bridges_radio_toggled) self.tor_bridges_no_bridges_radio.toggled.connect(self.tor_bridges_no_bridges_radio_toggled)
# obfs4 option radio # obfs4 option radio
# if the obfs4proxy binary is missing, we can't use obfs4 transports # if the obfs4proxy binary is missing, we can't use obfs4 transports
(self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths() (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths()
if not os.path.isfile(self.obfs4proxy_file_path): if not os.path.isfile(self.obfs4proxy_file_path):
self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy', True)) self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy'))
self.tor_bridges_use_obfs4_radio.setEnabled(False) self.tor_bridges_use_obfs4_radio.setEnabled(False)
else: else:
self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option', True)) self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option'))
self.tor_bridges_use_obfs4_radio.toggled.connect(self.tor_bridges_use_obfs4_radio_toggled) self.tor_bridges_use_obfs4_radio.toggled.connect(self.tor_bridges_use_obfs4_radio_toggled)
# meek_lite-azure option radio # meek_lite-azure option radio
# if the obfs4proxy binary is missing, we can't use meek_lite-azure transports # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports
(self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths() (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths()
if not os.path.isfile(self.obfs4proxy_file_path): if not os.path.isfile(self.obfs4proxy_file_path):
self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy', True)) self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy'))
self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False) self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False)
else: else:
self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option', True)) self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option'))
self.tor_bridges_use_meek_lite_azure_radio.toggled.connect(self.tor_bridges_use_meek_lite_azure_radio_toggled) self.tor_bridges_use_meek_lite_azure_radio.toggled.connect(self.tor_bridges_use_meek_lite_azure_radio_toggled)
# meek_lite currently not supported on the version of obfs4proxy bundled with TorBrowser # meek_lite currently not supported on the version of obfs4proxy bundled with TorBrowser
@ -268,10 +283,10 @@ class SettingsDialog(QtWidgets.QDialog):
self.tor_bridges_use_meek_lite_azure_radio.hide() self.tor_bridges_use_meek_lite_azure_radio.hide()
# Custom bridges radio and textbox # Custom bridges radio and textbox
self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_custom_radio_option', True)) self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_custom_radio_option'))
self.tor_bridges_use_custom_radio.toggled.connect(self.tor_bridges_use_custom_radio_toggled) self.tor_bridges_use_custom_radio.toggled.connect(self.tor_bridges_use_custom_radio_toggled)
self.tor_bridges_use_custom_label = QtWidgets.QLabel(strings._('gui_settings_tor_bridges_custom_label', True)) self.tor_bridges_use_custom_label = QtWidgets.QLabel(strings._('gui_settings_tor_bridges_custom_label'))
self.tor_bridges_use_custom_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) self.tor_bridges_use_custom_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
self.tor_bridges_use_custom_label.setOpenExternalLinks(True) self.tor_bridges_use_custom_label.setOpenExternalLinks(True)
self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit() self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit()
@ -298,14 +313,14 @@ class SettingsDialog(QtWidgets.QDialog):
self.bridges.setLayout(bridges_layout) self.bridges.setLayout(bridges_layout)
# Automatic # Automatic
self.connection_type_automatic_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_automatic_option', True)) self.connection_type_automatic_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_automatic_option'))
self.connection_type_automatic_radio.toggled.connect(self.connection_type_automatic_toggled) self.connection_type_automatic_radio.toggled.connect(self.connection_type_automatic_toggled)
# Control port # Control port
self.connection_type_control_port_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_control_port_option', True)) self.connection_type_control_port_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_control_port_option'))
self.connection_type_control_port_radio.toggled.connect(self.connection_type_control_port_toggled) self.connection_type_control_port_radio.toggled.connect(self.connection_type_control_port_toggled)
connection_type_control_port_extras_label = QtWidgets.QLabel(strings._('gui_settings_control_port_label', True)) connection_type_control_port_extras_label = QtWidgets.QLabel(strings._('gui_settings_control_port_label'))
self.connection_type_control_port_extras_address = QtWidgets.QLineEdit() self.connection_type_control_port_extras_address = QtWidgets.QLineEdit()
self.connection_type_control_port_extras_port = QtWidgets.QLineEdit() self.connection_type_control_port_extras_port = QtWidgets.QLineEdit()
connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout() connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout()
@ -318,10 +333,10 @@ class SettingsDialog(QtWidgets.QDialog):
self.connection_type_control_port_extras.hide() self.connection_type_control_port_extras.hide()
# Socket file # Socket file
self.connection_type_socket_file_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_socket_file_option', True)) self.connection_type_socket_file_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_socket_file_option'))
self.connection_type_socket_file_radio.toggled.connect(self.connection_type_socket_file_toggled) self.connection_type_socket_file_radio.toggled.connect(self.connection_type_socket_file_toggled)
connection_type_socket_file_extras_label = QtWidgets.QLabel(strings._('gui_settings_socket_file_label', True)) connection_type_socket_file_extras_label = QtWidgets.QLabel(strings._('gui_settings_socket_file_label'))
self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit() self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit()
connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout() connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout()
connection_type_socket_file_extras_layout.addWidget(connection_type_socket_file_extras_label) connection_type_socket_file_extras_layout.addWidget(connection_type_socket_file_extras_label)
@ -332,7 +347,7 @@ class SettingsDialog(QtWidgets.QDialog):
self.connection_type_socket_file_extras.hide() self.connection_type_socket_file_extras.hide()
# Tor SOCKS address and port # Tor SOCKS address and port
gui_settings_socks_label = QtWidgets.QLabel(strings._('gui_settings_socks_label', True)) gui_settings_socks_label = QtWidgets.QLabel(strings._('gui_settings_socks_label'))
self.connection_type_socks_address = QtWidgets.QLineEdit() self.connection_type_socks_address = QtWidgets.QLineEdit()
self.connection_type_socks_port = QtWidgets.QLineEdit() self.connection_type_socks_port = QtWidgets.QLineEdit()
connection_type_socks_layout = QtWidgets.QHBoxLayout() connection_type_socks_layout = QtWidgets.QHBoxLayout()
@ -347,14 +362,14 @@ class SettingsDialog(QtWidgets.QDialog):
# Authentication options # Authentication options
# No authentication # No authentication
self.authenticate_no_auth_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_no_auth_option', True)) self.authenticate_no_auth_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_no_auth_option'))
self.authenticate_no_auth_radio.toggled.connect(self.authenticate_no_auth_toggled) self.authenticate_no_auth_radio.toggled.connect(self.authenticate_no_auth_toggled)
# Password # Password
self.authenticate_password_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_password_option', True)) self.authenticate_password_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_password_option'))
self.authenticate_password_radio.toggled.connect(self.authenticate_password_toggled) self.authenticate_password_radio.toggled.connect(self.authenticate_password_toggled)
authenticate_password_extras_label = QtWidgets.QLabel(strings._('gui_settings_password_label', True)) authenticate_password_extras_label = QtWidgets.QLabel(strings._('gui_settings_password_label'))
self.authenticate_password_extras_password = QtWidgets.QLineEdit('') self.authenticate_password_extras_password = QtWidgets.QLineEdit('')
authenticate_password_extras_layout = QtWidgets.QHBoxLayout() authenticate_password_extras_layout = QtWidgets.QHBoxLayout()
authenticate_password_extras_layout.addWidget(authenticate_password_extras_label) authenticate_password_extras_layout.addWidget(authenticate_password_extras_label)
@ -369,7 +384,7 @@ class SettingsDialog(QtWidgets.QDialog):
authenticate_group_layout.addWidget(self.authenticate_no_auth_radio) authenticate_group_layout.addWidget(self.authenticate_no_auth_radio)
authenticate_group_layout.addWidget(self.authenticate_password_radio) authenticate_group_layout.addWidget(self.authenticate_password_radio)
authenticate_group_layout.addWidget(self.authenticate_password_extras) authenticate_group_layout.addWidget(self.authenticate_password_extras)
self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label", True)) self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label"))
self.authenticate_group.setLayout(authenticate_group_layout) self.authenticate_group.setLayout(authenticate_group_layout)
# Put the radios into their own group so they are exclusive # Put the radios into their own group so they are exclusive
@ -378,18 +393,18 @@ class SettingsDialog(QtWidgets.QDialog):
connection_type_radio_group_layout.addWidget(self.connection_type_automatic_radio) connection_type_radio_group_layout.addWidget(self.connection_type_automatic_radio)
connection_type_radio_group_layout.addWidget(self.connection_type_control_port_radio) connection_type_radio_group_layout.addWidget(self.connection_type_control_port_radio)
connection_type_radio_group_layout.addWidget(self.connection_type_socket_file_radio) connection_type_radio_group_layout.addWidget(self.connection_type_socket_file_radio)
connection_type_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_connection_type_label", True)) connection_type_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_connection_type_label"))
connection_type_radio_group.setLayout(connection_type_radio_group_layout) connection_type_radio_group.setLayout(connection_type_radio_group_layout)
# The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges) # The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges)
connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout()
connection_type_bridges_radio_group_layout.addWidget(self.bridges) connection_type_bridges_radio_group_layout.addWidget(self.bridges)
self.connection_type_bridges_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_tor_bridges", True)) self.connection_type_bridges_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_tor_bridges"))
self.connection_type_bridges_radio_group.setLayout(connection_type_bridges_radio_group_layout) self.connection_type_bridges_radio_group.setLayout(connection_type_bridges_radio_group_layout)
self.connection_type_bridges_radio_group.hide() self.connection_type_bridges_radio_group.hide()
# Test tor settings button # Test tor settings button
self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button', True)) self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button'))
self.connection_type_test_button.clicked.connect(self.test_tor_clicked) self.connection_type_test_button.clicked.connect(self.test_tor_clicked)
connection_type_test_button_layout = QtWidgets.QHBoxLayout() connection_type_test_button_layout = QtWidgets.QHBoxLayout()
connection_type_test_button_layout.addWidget(self.connection_type_test_button) connection_type_test_button_layout.addWidget(self.connection_type_test_button)
@ -405,13 +420,13 @@ class SettingsDialog(QtWidgets.QDialog):
connection_type_layout.addLayout(connection_type_test_button_layout) connection_type_layout.addLayout(connection_type_test_button_layout)
# Buttons # Buttons
self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save', True)) self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save'))
self.save_button.clicked.connect(self.save_clicked) self.save_button.clicked.connect(self.save_clicked)
self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel', True)) self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel'))
self.cancel_button.clicked.connect(self.cancel_clicked) self.cancel_button.clicked.connect(self.cancel_clicked)
version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(self.common.version)) version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(self.common.version))
version_label.setStyleSheet(self.common.css['settings_version']) version_label.setStyleSheet(self.common.css['settings_version'])
self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help', True)) self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help'))
self.help_button.clicked.connect(self.help_clicked) self.help_button.clicked.connect(self.help_clicked)
buttons_layout = QtWidgets.QHBoxLayout() buttons_layout = QtWidgets.QHBoxLayout()
buttons_layout.addWidget(version_label) buttons_layout.addWidget(version_label)
@ -431,6 +446,7 @@ class SettingsDialog(QtWidgets.QDialog):
left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(sharing_group)
left_col_layout.addWidget(receiving_group) left_col_layout.addWidget(receiving_group)
left_col_layout.addWidget(autoupdate_group) left_col_layout.addWidget(autoupdate_group)
left_col_layout.addLayout(language_layout)
left_col_layout.addStretch() left_col_layout.addStretch()
right_col_layout = QtWidgets.QVBoxLayout() right_col_layout = QtWidgets.QVBoxLayout()
@ -524,6 +540,10 @@ class SettingsDialog(QtWidgets.QDialog):
autoupdate_timestamp = self.old_settings.get('autoupdate_timestamp') autoupdate_timestamp = self.old_settings.get('autoupdate_timestamp')
self._update_autoupdate_timestamp(autoupdate_timestamp) self._update_autoupdate_timestamp(autoupdate_timestamp)
locale = self.old_settings.get('locale')
locale_index = self.language_combobox.findData(QtCore.QVariant(locale))
self.language_combobox.setCurrentIndex(locale_index)
connection_type = self.old_settings.get('connection_type') connection_type = self.old_settings.get('connection_type')
if connection_type == 'bundled': if connection_type == 'bundled':
if self.connection_type_bundled_radio.isEnabled(): if self.connection_type_bundled_radio.isEnabled():
@ -603,7 +623,7 @@ class SettingsDialog(QtWidgets.QDialog):
self.tor_bridges_use_custom_textbox_options.hide() self.tor_bridges_use_custom_textbox_options.hide()
# Alert the user about meek's costliness if it looks like they're turning it on # Alert the user about meek's costliness if it looks like they're turning it on
if not self.old_settings.get('tor_bridges_use_meek_lite_azure'): if not self.old_settings.get('tor_bridges_use_meek_lite_azure'):
Alert(self.common, strings._('gui_settings_meek_lite_expensive_warning', True), QtWidgets.QMessageBox.Warning) Alert(self.common, strings._('gui_settings_meek_lite_expensive_warning'), QtWidgets.QMessageBox.Warning)
def tor_bridges_use_custom_radio_toggled(self, checked): def tor_bridges_use_custom_radio_toggled(self, checked):
""" """
@ -716,7 +736,7 @@ class SettingsDialog(QtWidgets.QDialog):
""" """
downloads_dir = self.downloads_dir_lineedit.text() downloads_dir = self.downloads_dir_lineedit.text()
selected_dir = QtWidgets.QFileDialog.getExistingDirectory(self, selected_dir = QtWidgets.QFileDialog.getExistingDirectory(self,
strings._('gui_settings_downloads_label', True), downloads_dir) strings._('gui_settings_downloads_label'), downloads_dir)
if selected_dir: if selected_dir:
self.common.log('SettingsDialog', 'downloads_button_clicked', 'selected dir: {}'.format(selected_dir)) self.common.log('SettingsDialog', 'downloads_button_clicked', 'selected dir: {}'.format(selected_dir))
@ -746,7 +766,7 @@ class SettingsDialog(QtWidgets.QDialog):
onion.connect(custom_settings=settings, config=self.config, tor_status_update_func=tor_status_update_func) onion.connect(custom_settings=settings, config=self.config, tor_status_update_func=tor_status_update_func)
# If an exception hasn't been raised yet, the Tor settings work # If an exception hasn't been raised yet, the Tor settings work
Alert(self.common, strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth, onion.supports_next_gen_onions)) Alert(self.common, strings._('settings_test_success').format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth, onion.supports_next_gen_onions))
# Clean up # Clean up
onion.cleanup() onion.cleanup()
@ -782,19 +802,19 @@ class SettingsDialog(QtWidgets.QDialog):
# Check for updates # Check for updates
def update_available(update_url, installed_version, latest_version): def update_available(update_url, installed_version, latest_version):
Alert(self.common, strings._("update_available", True).format(update_url, installed_version, latest_version)) Alert(self.common, strings._("update_available").format(update_url, installed_version, latest_version))
close_forced_update_thread() close_forced_update_thread()
def update_not_available(): def update_not_available():
Alert(self.common, strings._('update_not_available', True)) Alert(self.common, strings._('update_not_available'))
close_forced_update_thread() close_forced_update_thread()
def update_error(): def update_error():
Alert(self.common, strings._('update_error_check_error', True), QtWidgets.QMessageBox.Warning) Alert(self.common, strings._('update_error_check_error'), QtWidgets.QMessageBox.Warning)
close_forced_update_thread() close_forced_update_thread()
def update_invalid_version(latest_version): def update_invalid_version(latest_version):
Alert(self.common, strings._('update_error_invalid_latest_version', True).format(latest_version), QtWidgets.QMessageBox.Warning) Alert(self.common, strings._('update_error_invalid_latest_version').format(latest_version), QtWidgets.QMessageBox.Warning)
close_forced_update_thread() close_forced_update_thread()
forced_update_thread = UpdateThread(self.common, self.onion, self.config, force=True) forced_update_thread = UpdateThread(self.common, self.onion, self.config, force=True)
@ -810,16 +830,6 @@ class SettingsDialog(QtWidgets.QDialog):
""" """
self.common.log('SettingsDialog', 'save_clicked') self.common.log('SettingsDialog', 'save_clicked')
settings = self.settings_from_fields()
if settings:
settings.save()
# If Tor isn't connected, or if Tor settings have changed, Reinitialize
# the Onion object
reboot_onion = False
if not self.local_only:
if self.onion.is_authenticated():
self.common.log('SettingsDialog', 'save_clicked', 'Connected to Tor')
def changed(s1, s2, keys): def changed(s1, s2, keys):
""" """
Compare the Settings objects s1 and s2 and return true if any values Compare the Settings objects s1 and s2 and return true if any values
@ -830,6 +840,28 @@ class SettingsDialog(QtWidgets.QDialog):
return True return True
return False return False
settings = self.settings_from_fields()
if settings:
# If language changed, inform user they need to restart OnionShare
if changed(settings, self.old_settings, ['locale']):
# Look up error message in different locale
new_locale = settings.get('locale')
if new_locale in strings.translations and 'gui_settings_language_changed_notice' in strings.translations[new_locale]:
notice = strings.translations[new_locale]['gui_settings_language_changed_notice']
else:
notice = strings._('gui_settings_language_changed_notice')
Alert(self.common, notice, QtWidgets.QMessageBox.Information)
# Save the new settings
settings.save()
# If Tor isn't connected, or if Tor settings have changed, Reinitialize
# the Onion object
reboot_onion = False
if not self.local_only:
if self.onion.is_authenticated():
self.common.log('SettingsDialog', 'save_clicked', 'Connected to Tor')
if changed(settings, self.old_settings, [ if changed(settings, self.old_settings, [
'connection_type', 'control_port_address', 'connection_type', 'control_port_address',
'control_port_port', 'socks_address', 'socks_port', 'control_port_port', 'socks_address', 'socks_port',
@ -873,7 +905,7 @@ class SettingsDialog(QtWidgets.QDialog):
""" """
self.common.log('SettingsDialog', 'cancel_clicked') self.common.log('SettingsDialog', 'cancel_clicked')
if not self.local_only and not self.onion.is_authenticated(): if not self.local_only and not self.onion.is_authenticated():
Alert(self.common, strings._('gui_tor_connection_canceled', True), QtWidgets.QMessageBox.Warning) Alert(self.common, strings._('gui_tor_connection_canceled'), QtWidgets.QMessageBox.Warning)
sys.exit() sys.exit()
else: else:
self.close() self.close()
@ -940,6 +972,12 @@ class SettingsDialog(QtWidgets.QDialog):
if not self.stealth_checkbox.isChecked(): if not self.stealth_checkbox.isChecked():
settings.set('hidservauth_string', '') settings.set('hidservauth_string', '')
# Language
locale_index = self.language_combobox.currentIndex()
locale = self.language_combobox.itemData(locale_index)
settings.set('locale', locale)
# Tor connection
if self.connection_type_bundled_radio.isChecked(): if self.connection_type_bundled_radio.isChecked():
settings.set('connection_type', 'bundled') settings.set('connection_type', 'bundled')
if self.connection_type_automatic_radio.isChecked(): if self.connection_type_automatic_radio.isChecked():
@ -1013,7 +1051,7 @@ class SettingsDialog(QtWidgets.QDialog):
new_bridges = ''.join(new_bridges) new_bridges = ''.join(new_bridges)
settings.set('tor_bridges_use_custom_bridges', new_bridges) settings.set('tor_bridges_use_custom_bridges', new_bridges)
else: else:
Alert(self.common, strings._('gui_settings_tor_bridges_invalid', True)) Alert(self.common, strings._('gui_settings_tor_bridges_invalid'))
settings.set('no_bridges', True) settings.set('no_bridges', True)
return False return False
@ -1037,11 +1075,11 @@ class SettingsDialog(QtWidgets.QDialog):
dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) dt = datetime.datetime.fromtimestamp(autoupdate_timestamp)
last_checked = dt.strftime('%B %d, %Y %H:%M') last_checked = dt.strftime('%B %d, %Y %H:%M')
else: else:
last_checked = strings._('gui_settings_autoupdate_timestamp_never', True) last_checked = strings._('gui_settings_autoupdate_timestamp_never')
self.autoupdate_timestamp.setText(strings._('gui_settings_autoupdate_timestamp', True).format(last_checked)) self.autoupdate_timestamp.setText(strings._('gui_settings_autoupdate_timestamp').format(last_checked))
def _tor_status_update(self, progress, summary): def _tor_status_update(self, progress, summary):
self.tor_status.setText('<strong>{}</strong><br>{}% {}'.format(strings._('connecting_to_tor', True), progress, summary)) self.tor_status.setText('<strong>{}</strong><br>{}% {}'.format(strings._('connecting_to_tor'), progress, summary))
self.qtapp.processEvents() self.qtapp.processEvents()
if 'Done' in summary: if 'Done' in summary:
self.tor_status.hide() self.tor_status.hide()

View File

@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import time
from PyQt5 import QtCore, QtWidgets, QtGui
from onionshare import strings
class Download(object):
def __init__(self, common, download_id, total_bytes):
self.common = common
self.download_id = download_id
self.started = time.time()
self.total_bytes = total_bytes
self.downloaded_bytes = 0
# Progress bar
self.progress_bar = QtWidgets.QProgressBar()
self.progress_bar.setTextVisible(True)
self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter)
self.progress_bar.setMinimum(0)
self.progress_bar.setMaximum(total_bytes)
self.progress_bar.setValue(0)
self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar'])
self.progress_bar.total_bytes = total_bytes
# Start at 0
self.update(0)
def update(self, downloaded_bytes):
self.downloaded_bytes = downloaded_bytes
self.progress_bar.setValue(downloaded_bytes)
if downloaded_bytes == self.progress_bar.total_bytes:
pb_fmt = strings._('gui_download_upload_progress_complete').format(
self.common.format_seconds(time.time() - self.started))
else:
elapsed = time.time() - self.started
if elapsed < 10:
# Wait a couple of seconds for the download rate to stabilize.
# This prevents a "Windows copy dialog"-esque experience at
# the beginning of the download.
pb_fmt = strings._('gui_download_upload_progress_starting').format(
self.common.human_readable_filesize(downloaded_bytes))
else:
pb_fmt = strings._('gui_download_upload_progress_eta').format(
self.common.human_readable_filesize(downloaded_bytes),
self.estimated_time_remaining)
self.progress_bar.setFormat(pb_fmt)
def cancel(self):
self.progress_bar.setFormat(strings._('gui_canceled'))
@property
def estimated_time_remaining(self):
return self.common.estimated_time_remaining(self.downloaded_bytes,
self.total_bytes,
self.started)
class Downloads(QtWidgets.QScrollArea):
"""
The downloads chunk of the GUI. This lists all of the active download
progress bars.
"""
def __init__(self, common):
super(Downloads, self).__init__()
self.common = common
self.downloads = {}
self.setWindowTitle(strings._('gui_downloads'))
self.setWidgetResizable(True)
self.setMinimumHeight(150)
self.setMinimumWidth(350)
self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
self.setWindowFlags(QtCore.Qt.Sheet | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.CustomizeWindowHint)
self.vbar = self.verticalScrollBar()
self.vbar.rangeChanged.connect(self.resizeScroll)
downloads_label = QtWidgets.QLabel(strings._('gui_downloads'))
downloads_label.setStyleSheet(self.common.css['downloads_uploads_label'])
self.no_downloads_label = QtWidgets.QLabel(strings._('gui_no_downloads'))
self.clear_history_button = QtWidgets.QPushButton(strings._('gui_clear_history'))
self.clear_history_button.clicked.connect(self.reset)
self.clear_history_button.hide()
self.downloads_layout = QtWidgets.QVBoxLayout()
widget = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(downloads_label)
layout.addWidget(self.no_downloads_label)
layout.addWidget(self.clear_history_button)
layout.addLayout(self.downloads_layout)
layout.addStretch()
widget.setLayout(layout)
self.setWidget(widget)
def resizeScroll(self, minimum, maximum):
"""
Scroll to the bottom of the window when the range changes.
"""
self.vbar.setValue(maximum)
def add(self, download_id, total_bytes):
"""
Add a new download progress bar.
"""
# Hide the no_downloads_label
self.no_downloads_label.hide()
# Show the clear_history_button
self.clear_history_button.show()
# Add it to the list
download = Download(self.common, download_id, total_bytes)
self.downloads[download_id] = download
self.downloads_layout.addWidget(download.progress_bar)
def update(self, download_id, downloaded_bytes):
"""
Update the progress of a download progress bar.
"""
self.downloads[download_id].update(downloaded_bytes)
def cancel(self, download_id):
"""
Update a download progress bar to show that it has been canceled.
"""
self.downloads[download_id].cancel()
def reset(self):
"""
Reset the downloads back to zero
"""
for download in self.downloads.values():
self.downloads_layout.removeWidget(download.progress_bar)
download.progress_bar.close()
self.downloads = {}
self.no_downloads_label.show()
self.clear_history_button.hide()
self.resize(self.sizeHint())

View File

@ -51,7 +51,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
self.setFixedSize(400, 150) self.setFixedSize(400, 150)
# Label # Label
self.setLabelText(strings._('connecting_to_tor', True)) self.setLabelText(strings._('connecting_to_tor'))
# Progress bar ticks from 0 to 100 # Progress bar ticks from 0 to 100
self.setRange(0, 100) self.setRange(0, 100)
@ -81,7 +81,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
def _tor_status_update(self, progress, summary): def _tor_status_update(self, progress, summary):
self.setValue(int(progress)) self.setValue(int(progress))
self.setLabelText("<strong>{}</strong><br>{}".format(strings._('connecting_to_tor', True), summary)) self.setLabelText("<strong>{}</strong><br>{}".format(strings._('connecting_to_tor'), summary))
def _connected_to_tor(self): def _connected_to_tor(self):
self.common.log('TorConnectionDialog', '_connected_to_tor') self.common.log('TorConnectionDialog', '_connected_to_tor')
@ -104,7 +104,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
def alert_and_open_settings(): def alert_and_open_settings():
# Display the exception in an alert box # Display the exception in an alert box
Alert(self.common, "{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings', True)), QtWidgets.QMessageBox.Warning) Alert(self.common, "{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings')), QtWidgets.QMessageBox.Warning)
# Open settings # Open settings
self.open_settings.emit() self.open_settings.emit()

View File

@ -106,7 +106,6 @@
"gui_settings_button_help": "Help", "gui_settings_button_help": "Help",
"gui_settings_shutdown_timeout_checkbox": "Use auto-stop timer", "gui_settings_shutdown_timeout_checkbox": "Use auto-stop timer",
"gui_settings_shutdown_timeout": "Stop the share at:", "gui_settings_shutdown_timeout": "Stop the share at:",
"settings_saved": "Settings saved in {}",
"settings_error_unknown": "Can't connect to Tor controller because your settings don't make sense.", "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_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 {}:{}.", "settings_error_socket_port": "Can't connect to the Tor controller at {}:{}.",
@ -181,5 +180,7 @@
"gui_upload_finished_range": "Uploaded {} to {}", "gui_upload_finished_range": "Uploaded {} to {}",
"gui_upload_finished": "Uploaded {}", "gui_upload_finished": "Uploaded {}",
"gui_download_in_progress": "Download Started {}", "gui_download_in_progress": "Download Started {}",
"gui_open_folder_error_nautilus": "Cannot open folder because nautilus is not available. The file is here: {}" "gui_open_folder_error_nautilus": "Cannot open folder because nautilus is not available. The file is here: {}",
"gui_settings_language_label": "Preferred language",
"gui_settings_language_changed_notice": "Restart OnionShare for your change in language to take effect."
} }

View File

@ -29,5 +29,6 @@
"gui_please_wait": "Attendez-vous...", "gui_please_wait": "Attendez-vous...",
"gui_quit_warning_quit": "Quitter", "gui_quit_warning_quit": "Quitter",
"gui_quit_warning_dont_quit": "Ne quitter pas", "gui_quit_warning_dont_quit": "Ne quitter pas",
"gui_settings_autoupdate_timestamp_never": "Jamais" "gui_settings_autoupdate_timestamp_never": "Jamais",
"gui_settings_language_changed_notice": "Redémarrez OnionShare pour que votre changement de langue prenne effet"
} }

View File

@ -8,7 +8,7 @@ import tempfile
import pytest import pytest
from onionshare import common, web, settings from onionshare import common, web, settings, strings
@pytest.fixture @pytest.fixture
def temp_dir_1024(): def temp_dir_1024():
@ -151,7 +151,10 @@ def time_strftime(monkeypatch):
@pytest.fixture @pytest.fixture
def common_obj(): def common_obj():
return common.Common() _common = common.Common()
_common.settings = settings.Settings(_common)
strings.load_strings(_common)
return _common
@pytest.fixture @pytest.fixture
def settings_obj(sys_onionshare_dev_mode, platform_linux): def settings_obj(sys_onionshare_dev_mode, platform_linux):

View File

@ -40,7 +40,7 @@ def settings_obj(sys_onionshare_dev_mode, platform_linux):
class TestSettings: class TestSettings:
def test_init(self, settings_obj): def test_init(self, settings_obj):
assert settings_obj._settings == settings_obj.default_settings == { expected_settings = {
'version': 'DUMMY_VERSION_1.2.3', 'version': 'DUMMY_VERSION_1.2.3',
'connection_type': 'bundled', 'connection_type': 'bundled',
'control_port_address': '127.0.0.1', 'control_port_address': '127.0.0.1',
@ -68,6 +68,11 @@ class TestSettings:
'receive_allow_receiver_shutdown': True, 'receive_allow_receiver_shutdown': True,
'public_mode': False 'public_mode': False
} }
for key in settings_obj._settings:
# Skip locale, it will not always default to the same thing
if key != 'locale':
assert settings_obj._settings[key] == settings_obj.default_settings[key]
assert settings_obj._settings[key] == expected_settings[key]
def test_fill_in_defaults(self, settings_obj): def test_fill_in_defaults(self, settings_obj):
del settings_obj._settings['version'] del settings_obj._settings['version']

View File

@ -32,12 +32,6 @@ from onionshare import strings
# return path # return path
# common.get_resource_path = get_resource_path # common.get_resource_path = get_resource_path
def test_starts_with_empty_strings():
""" Creates an empty strings dict by default """
assert strings.strings == {}
def test_underscore_is_function(): def test_underscore_is_function():
assert callable(strings._) and isinstance(strings._, types.FunctionType) assert callable(strings._) and isinstance(strings._, types.FunctionType)
@ -53,11 +47,13 @@ class TestLoadStrings:
def test_load_strings_loads_other_languages( def test_load_strings_loads_other_languages(
self, common_obj, locale_fr, sys_onionshare_dev_mode): self, common_obj, locale_fr, sys_onionshare_dev_mode):
""" load_strings() loads other languages in different locales """ """ load_strings() loads other languages in different locales """
strings.load_strings(common_obj, "fr") common_obj.settings.set('locale', 'fr')
strings.load_strings(common_obj)
assert strings._('preparing_files') == "Préparation des fichiers à partager." assert strings._('preparing_files') == "Préparation des fichiers à partager."
def test_load_invalid_locale( def test_load_invalid_locale(
self, common_obj, locale_invalid, sys_onionshare_dev_mode): self, common_obj, locale_invalid, sys_onionshare_dev_mode):
""" load_strings() raises a KeyError for an invalid locale """ """ load_strings() raises a KeyError for an invalid locale """
with pytest.raises(KeyError): with pytest.raises(KeyError):
strings.load_strings(common_obj, 'XX') common_obj.settings.set('locale', 'XX')
strings.load_strings(common_obj)

View File

@ -3,14 +3,62 @@ import requests
import socket import socket
import socks import socks
import zipfile import zipfile
import json
import shutil
from PyQt5 import QtCore, QtTest from PyQt5 import QtCore, QtTest
from onionshare import strings from onionshare import strings
from onionshare_gui.mode.receive_mode import ReceiveMode from onionshare.common import Common
from onionshare.settings import Settings
from onionshare.onion import Onion
from onionshare.web import Web
from onionshare_gui import Application, OnionShare, OnionShareGui
from onionshare_gui.mode.share_mode import ShareMode from onionshare_gui.mode.share_mode import ShareMode
from onionshare_gui.mode.receive_mode import ReceiveMode
class CommonTests(object): class CommonTests(object):
@staticmethod
def set_up(test_settings):
'''Create GUI with given settings'''
# Create our test file
testfile = open('/tmp/test.txt', 'w')
testfile.write('onionshare')
testfile.close()
common = Common()
common.settings = Settings(common)
common.define_css()
strings.load_strings(common)
# Get all of the settings in test_settings
test_settings['downloads_dir'] = '/tmp/OnionShare'
for key, val in common.settings.default_settings.items():
if key not in test_settings:
test_settings[key] = val
# Start the Onion
testonion = Onion(common)
global qtapp
qtapp = Application(common)
app = OnionShare(common, testonion, True, 0)
web = Web(common, False, True)
settings_filename = '/tmp/testsettings.json'
open(settings_filename, 'w').write(json.dumps(test_settings))
gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], settings_filename, True)
return gui
@staticmethod
def tear_down():
try:
os.remove('/tmp/test.txt')
shutil.rmtree('/tmp/OnionShare')
except:
pass
def test_gui_loaded(self): def test_gui_loaded(self):
'''Test that the GUI actually is shown''' '''Test that the GUI actually is shown'''
self.assertTrue(self.gui.show) self.assertTrue(self.gui.show)
@ -190,12 +238,12 @@ class CommonTests(object):
def test_server_status_indicator_says_closed(self, mode, stay_open): def test_server_status_indicator_says_closed(self, mode, stay_open):
'''Test that the Server Status indicator shows we closed''' '''Test that the Server Status indicator shows we closed'''
if type(mode) == ReceiveMode: if type(mode) == ReceiveMode:
self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True)) self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped'))
if type(mode) == ShareMode: if type(mode) == ShareMode:
if stay_open: if stay_open:
self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped', True)) self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped'))
else: else:
self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True)) self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically'))
# Auto-stop timer tests # Auto-stop timer tests
def test_set_timeout(self, mode, timeout): def test_set_timeout(self, mode, timeout):

View File

@ -15,68 +15,17 @@ from onionshare_gui import *
from .commontests import CommonTests from .commontests import CommonTests
class OnionShareGuiTest(unittest.TestCase): class OnionShareGuiTest(unittest.TestCase):
'''Test the OnionShare GUI'''
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
'''Create the GUI'''
# Create our test file
testfile = open('/tmp/test.txt', 'w')
testfile.write('onionshare')
testfile.close()
common = Common()
common.define_css()
# Start the Onion
strings.load_strings(common)
testonion = onion.Onion(common)
global qtapp
qtapp = Application(common)
app = OnionShare(common, testonion, True, 0)
web = Web(common, False, True)
test_settings = { test_settings = {
"auth_password": "",
"auth_type": "no_auth",
"autoupdate_timestamp": "",
"close_after_first_download": True,
"connection_type": "bundled",
"control_port_address": "127.0.0.1",
"control_port_port": 9051,
"downloads_dir": "/tmp/OnionShare",
"hidservauth_string": "",
"no_bridges": True,
"private_key": "",
"public_mode": False, "public_mode": False,
"receive_allow_receiver_shutdown": True, "receive_allow_receiver_shutdown": True
"save_private_key": False,
"shutdown_timeout": False,
"slug": "",
"socks_address": "127.0.0.1",
"socks_port": 9050,
"socket_file_path": "/var/run/tor/control",
"systray_notifications": True,
"tor_bridges_use_meek_lite_azure": False,
"tor_bridges_use_meek_lite_amazon": False,
"tor_bridges_use_custom_bridges": "",
"tor_bridges_use_obfs4": False,
"use_stealth": False,
"use_legacy_v2_onions": False,
"use_autoupdate": True,
"version": "1.3.1"
} }
testsettings = '/tmp/testsettings.json' cls.gui = CommonTests.set_up(test_settings)
open(testsettings, 'w').write(json.dumps(test_settings))
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True)
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
'''Clean up after tests''' CommonTests.tear_down()
os.remove('/tmp/test.txt')
os.remove('/tmp/OnionShare/test.txt')
os.remove('/tmp/OnionShare/test-2.txt')
@pytest.mark.run(order=1) @pytest.mark.run(order=1)
def test_gui_loaded(self): def test_gui_loaded(self):

View File

@ -8,6 +8,7 @@ import json
from PyQt5 import QtWidgets from PyQt5 import QtWidgets
from onionshare.common import Common from onionshare.common import Common
from onionshare.settings import Settings
from onionshare.web import Web from onionshare.web import Web
from onionshare import onion, strings from onionshare import onion, strings
from onionshare_gui import * from onionshare_gui import *
@ -15,71 +16,17 @@ from onionshare_gui import *
from .commontests import CommonTests from .commontests import CommonTests
class OnionShareGuiTest(unittest.TestCase): class OnionShareGuiTest(unittest.TestCase):
'''Test the OnionShare GUI'''
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
'''Create the GUI'''
# Create our test file
testfile = open('/tmp/test.txt', 'w')
testfile.write('onionshare')
testfile.close()
common = Common()
common.define_css()
# Start the Onion
strings.load_strings(common)
testonion = onion.Onion(common)
global qtapp
qtapp = Application(common)
app = OnionShare(common, testonion, True, 0)
web = Web(common, False, True)
test_settings = { test_settings = {
"auth_password": "",
"auth_type": "no_auth",
"autoupdate_timestamp": "",
"close_after_first_download": True,
"connection_type": "bundled",
"control_port_address": "127.0.0.1",
"control_port_port": 9051,
"downloads_dir": "/tmp/OnionShare",
"hidservauth_string": "",
"no_bridges": True,
"private_key": "",
"public_mode": True, "public_mode": True,
"receive_allow_receiver_shutdown": True, "receive_allow_receiver_shutdown": True
"save_private_key": False,
"shutdown_timeout": False,
"slug": "",
"socks_address": "127.0.0.1",
"socks_port": 9050,
"socket_file_path": "/var/run/tor/control",
"systray_notifications": True,
"tor_bridges_use_meek_lite_azure": False,
"tor_bridges_use_meek_lite_amazon": False,
"tor_bridges_use_custom_bridges": "",
"tor_bridges_use_obfs4": False,
"use_stealth": False,
"use_legacy_v2_onions": False,
"use_autoupdate": True,
"version": "1.3.1"
} }
testsettings = '/tmp/testsettings.json' cls.gui = CommonTests.set_up(test_settings)
open(testsettings, 'w').write(json.dumps(test_settings))
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True)
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
'''Clean up after tests''' CommonTests.tear_down()
try:
os.remove('/tmp/test.txt')
os.remove('/tmp/OnionShare/test.txt')
os.remove('/tmp/OnionShare/test-2.txt')
except:
pass
@pytest.mark.run(order=1) @pytest.mark.run(order=1)
def test_gui_loaded(self): def test_gui_loaded(self):

View File

@ -15,66 +15,17 @@ from onionshare_gui import *
from .commontests import CommonTests from .commontests import CommonTests
class OnionShareGuiTest(unittest.TestCase): class OnionShareGuiTest(unittest.TestCase):
'''Test the OnionShare GUI'''
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
'''Create the GUI'''
# Create our test file
testfile = open('/tmp/test.txt', 'w')
testfile.write('onionshare')
testfile.close()
common = Common()
common.define_css()
# Start the Onion
strings.load_strings(common)
testonion = onion.Onion(common)
global qtapp
qtapp = Application(common)
app = OnionShare(common, testonion, True, 0)
web = Web(common, False, True)
test_settings = { test_settings = {
"auth_password": "",
"auth_type": "no_auth",
"autoupdate_timestamp": "",
"close_after_first_download": True,
"connection_type": "bundled",
"control_port_address": "127.0.0.1",
"control_port_port": 9051,
"downloads_dir": "/tmp/OnionShare",
"hidservauth_string": "",
"no_bridges": True,
"private_key": "",
"public_mode": False, "public_mode": False,
"receive_allow_receiver_shutdown": True, "close_after_first_download": True
"save_private_key": False,
"shutdown_timeout": False,
"slug": "",
"socks_address": "127.0.0.1",
"socks_port": 9050,
"socket_file_path": "/var/run/tor/control",
"systray_notifications": True,
"tor_bridges_use_meek_lite_azure": False,
"tor_bridges_use_meek_lite_amazon": False,
"tor_bridges_use_custom_bridges": "",
"tor_bridges_use_obfs4": False,
"use_stealth": False,
"use_legacy_v2_onions": False,
"use_autoupdate": True,
"version": "1.3.1"
} }
testsettings = '/tmp/testsettings.json' cls.gui = CommonTests.set_up(test_settings)
open(testsettings, 'w').write(json.dumps(test_settings))
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True)
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
'''Clean up after tests''' CommonTests.tear_down()
os.remove('/tmp/test.txt')
@pytest.mark.run(order=1) @pytest.mark.run(order=1)
def test_gui_loaded(self): def test_gui_loaded(self):

View File

@ -15,66 +15,16 @@ from onionshare_gui import *
from .commontests import CommonTests from .commontests import CommonTests
class OnionShareGuiTest(unittest.TestCase): class OnionShareGuiTest(unittest.TestCase):
'''Test the OnionShare GUI'''
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
'''Create the GUI'''
# Create our test file
testfile = open('/tmp/test.txt', 'w')
testfile.write('onionshare')
testfile.close()
common = Common()
common.define_css()
# Start the Onion
strings.load_strings(common)
testonion = onion.Onion(common)
global qtapp
qtapp = Application(common)
app = OnionShare(common, testonion, True, 0)
web = Web(common, False, True)
test_settings = { test_settings = {
"auth_password": "", "public_mode": True
"auth_type": "no_auth",
"autoupdate_timestamp": "",
"close_after_first_download": True,
"connection_type": "bundled",
"control_port_address": "127.0.0.1",
"control_port_port": 9051,
"downloads_dir": "/tmp/OnionShare",
"hidservauth_string": "",
"no_bridges": True,
"private_key": "",
"public_mode": True,
"receive_allow_receiver_shutdown": True,
"save_private_key": False,
"shutdown_timeout": False,
"slug": "",
"socks_address": "127.0.0.1",
"socks_port": 9050,
"socket_file_path": "/var/run/tor/control",
"systray_notifications": True,
"tor_bridges_use_meek_lite_azure": False,
"tor_bridges_use_meek_lite_amazon": False,
"tor_bridges_use_custom_bridges": "",
"tor_bridges_use_obfs4": False,
"use_stealth": False,
"use_legacy_v2_onions": False,
"use_autoupdate": True,
"version": "1.3.1"
} }
testsettings = '/tmp/testsettings.json' cls.gui = CommonTests.set_up(test_settings)
open(testsettings, 'w').write(json.dumps(test_settings))
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True)
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
'''Clean up after tests''' CommonTests.tear_down()
os.remove('/tmp/test.txt')
@pytest.mark.run(order=1) @pytest.mark.run(order=1)
def test_gui_loaded(self): def test_gui_loaded(self):

View File

@ -15,66 +15,17 @@ from onionshare_gui import *
from .commontests import CommonTests from .commontests import CommonTests
class OnionShareGuiTest(unittest.TestCase): class OnionShareGuiTest(unittest.TestCase):
'''Test the OnionShare GUI'''
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
'''Create the GUI'''
# Create our test file
testfile = open('/tmp/test.txt', 'w')
testfile.write('onionshare')
testfile.close()
common = Common()
common.define_css()
# Start the Onion
strings.load_strings(common)
testonion = onion.Onion(common)
global qtapp
qtapp = Application(common)
app = OnionShare(common, testonion, True, 0)
web = Web(common, False, True)
test_settings = { test_settings = {
"auth_password": "",
"auth_type": "no_auth",
"autoupdate_timestamp": "",
"close_after_first_download": False,
"connection_type": "bundled",
"control_port_address": "127.0.0.1",
"control_port_port": 9051,
"downloads_dir": "/tmp/OnionShare",
"hidservauth_string": "",
"no_bridges": True,
"private_key": "",
"public_mode": True, "public_mode": True,
"receive_allow_receiver_shutdown": True, "close_after_first_download": False
"save_private_key": False,
"shutdown_timeout": False,
"slug": "",
"socks_address": "127.0.0.1",
"socks_port": 9050,
"socket_file_path": "/var/run/tor/control",
"systray_notifications": True,
"tor_bridges_use_meek_lite_azure": False,
"tor_bridges_use_meek_lite_amazon": False,
"tor_bridges_use_custom_bridges": "",
"tor_bridges_use_obfs4": False,
"use_stealth": False,
"use_legacy_v2_onions": False,
"use_autoupdate": True,
"version": "1.3.1"
} }
testsettings = '/tmp/testsettings.json' cls.gui = CommonTests.set_up(test_settings)
open(testsettings, 'w').write(json.dumps(test_settings))
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True)
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
'''Clean up after tests''' CommonTests.tear_down()
os.remove('/tmp/test.txt')
@pytest.mark.run(order=1) @pytest.mark.run(order=1)
def test_gui_loaded(self): def test_gui_loaded(self):

View File

@ -15,68 +15,18 @@ from onionshare_gui import *
from .commontests import CommonTests from .commontests import CommonTests
class OnionShareGuiTest(unittest.TestCase): class OnionShareGuiTest(unittest.TestCase):
'''Test the OnionShare GUI'''
slug = ''
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
'''Create the GUI'''
# Create our test file
testfile = open('/tmp/test.txt', 'w')
testfile.write('onionshare')
testfile.close()
common = Common()
common.define_css()
# Start the Onion
strings.load_strings(common)
testonion = onion.Onion(common)
global qtapp
qtapp = Application(common)
app = OnionShare(common, testonion, True, 0)
web = Web(common, False, True)
test_settings = { test_settings = {
"auth_password": "",
"auth_type": "no_auth",
"autoupdate_timestamp": "",
"close_after_first_download": True,
"connection_type": "bundled",
"control_port_address": "127.0.0.1",
"control_port_port": 9051,
"downloads_dir": "/tmp/OnionShare",
"hidservauth_string": "",
"no_bridges": True,
"private_key": "",
"public_mode": False, "public_mode": False,
"receive_allow_receiver_shutdown": True,
"save_private_key": True,
"shutdown_timeout": False,
"slug": "", "slug": "",
"socks_address": "127.0.0.1", "save_private_key": True
"socks_port": 9050,
"socket_file_path": "/var/run/tor/control",
"systray_notifications": True,
"tor_bridges_use_meek_lite_azure": False,
"tor_bridges_use_meek_lite_amazon": False,
"tor_bridges_use_custom_bridges": "",
"tor_bridges_use_obfs4": False,
"use_stealth": False,
"use_legacy_v2_onions": False,
"use_autoupdate": True,
"version": "1.3.1"
} }
testsettings = '/tmp/testsettings.json' cls.gui = CommonTests.set_up(test_settings)
open(testsettings, 'w').write(json.dumps(test_settings))
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True)
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
'''Clean up after tests''' CommonTests.tear_down()
os.remove('/tmp/test.txt')
@pytest.mark.run(order=1) @pytest.mark.run(order=1)
def test_gui_loaded(self): def test_gui_loaded(self):

View File

@ -15,66 +15,17 @@ from onionshare_gui import *
from .commontests import CommonTests from .commontests import CommonTests
class OnionShareGuiTest(unittest.TestCase): class OnionShareGuiTest(unittest.TestCase):
'''Test the OnionShare GUI'''
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
'''Create the GUI'''
# Create our test file
testfile = open('/tmp/test.txt', 'w')
testfile.write('onionshare')
testfile.close()
common = Common()
common.define_css()
# Start the Onion
strings.load_strings(common)
testonion = onion.Onion(common)
global qtapp
qtapp = Application(common)
app = OnionShare(common, testonion, True, 0)
web = Web(common, False, True)
test_settings = { test_settings = {
"auth_password": "",
"auth_type": "no_auth",
"autoupdate_timestamp": "",
"close_after_first_download": True,
"connection_type": "bundled",
"control_port_address": "127.0.0.1",
"control_port_port": 9051,
"downloads_dir": "/tmp/OnionShare",
"hidservauth_string": "",
"no_bridges": True,
"private_key": "",
"public_mode": False, "public_mode": False,
"receive_allow_receiver_shutdown": True, "shutdown_timeout": True
"save_private_key": False,
"shutdown_timeout": True,
"slug": "",
"socks_address": "127.0.0.1",
"socks_port": 9050,
"socket_file_path": "/var/run/tor/control",
"systray_notifications": True,
"tor_bridges_use_meek_lite_azure": False,
"tor_bridges_use_meek_lite_amazon": False,
"tor_bridges_use_custom_bridges": "",
"tor_bridges_use_obfs4": False,
"use_stealth": False,
"use_legacy_v2_onions": False,
"use_autoupdate": True,
"version": "1.3.1"
} }
testsettings = '/tmp/testsettings.json' cls.gui = CommonTests.set_up(test_settings)
open(testsettings, 'w').write(json.dumps(test_settings))
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True)
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
'''Clean up after tests''' CommonTests.tear_down()
os.remove('/tmp/test.txt')
@pytest.mark.run(order=1) @pytest.mark.run(order=1)
def test_gui_loaded(self): def test_gui_loaded(self):

View File

@ -56,6 +56,6 @@ class CommonTests(LocalCommonTests):
self.gui.app.onion.cleanup(stop_tor=True) self.gui.app.onion.cleanup(stop_tor=True)
QtTest.QTest.qWait(2500) QtTest.QTest.qWait(2500)
if mode == 'share': if mode == 'share':
self.assertTrue(self.gui.share_mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost', True)) self.assertTrue(self.gui.share_mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost'))
if mode == 'receive': if mode == 'receive':
self.assertTrue(self.gui.receive_mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost', True)) self.assertTrue(self.gui.receive_mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost'))

View File

@ -8,7 +8,7 @@ import tempfile
import pytest import pytest
from onionshare import common, web, settings from onionshare import common, web, settings, strings
@pytest.fixture @pytest.fixture
def temp_dir_1024(): def temp_dir_1024():
@ -151,7 +151,10 @@ def time_strftime(monkeypatch):
@pytest.fixture @pytest.fixture
def common_obj(): def common_obj():
return common.Common() _common = common.Common()
_common.settings = settings.Settings(_common)
strings.load_strings(_common)
return _common
@pytest.fixture @pytest.fixture
def settings_obj(sys_onionshare_dev_mode, platform_linux): def settings_obj(sys_onionshare_dev_mode, platform_linux):